From 14a95775c3b7c39b8e526bf76abe91b5bda3fde2 Mon Sep 17 00:00:00 2001 From: Sampras lopes Date: Wed, 22 Nov 2023 13:07:08 +0530 Subject: [PATCH 01/19] feat(events): update connector impl --- crates/common_utils/src/request.rs | 65 ++++++++----------- crates/router/src/connector/aci.rs | 13 ++-- crates/router/src/connector/adyen.rs | 40 ++++++------ crates/router/src/connector/airwallex.rs | 24 +++---- .../router/src/connector/authorizedotnet.rs | 24 +++---- crates/router/src/connector/bambora.rs | 26 ++++---- crates/router/src/connector/bankofamerica.rs | 25 +++---- crates/router/src/connector/bitpay.rs | 6 +- crates/router/src/connector/bluesnap.rs | 26 ++++---- crates/router/src/connector/boku.rs | 26 ++++---- crates/router/src/connector/braintree.rs | 28 ++++---- crates/router/src/connector/cashtocode.rs | 6 +- crates/router/src/connector/checkout.rs | 22 +++---- crates/router/src/connector/coinbase.rs | 6 +- crates/router/src/connector/cryptopay.rs | 7 +- crates/router/src/connector/cybersource.rs | 26 ++++---- crates/router/src/connector/dlocal.rs | 18 ++--- crates/router/src/connector/dummyconnector.rs | 14 ++-- crates/router/src/connector/fiserv.rs | 34 +++++----- crates/router/src/connector/forte.rs | 16 ++--- crates/router/src/connector/globalpay.rs | 32 ++++----- crates/router/src/connector/globepay.rs | 10 +-- crates/router/src/connector/gocardless.rs | 31 ++++----- crates/router/src/connector/helcim.rs | 26 ++++---- crates/router/src/connector/iatapay.rs | 18 ++--- crates/router/src/connector/klarna.rs | 8 +-- crates/router/src/connector/mollie.rs | 16 ++--- crates/router/src/connector/multisafepay.rs | 13 ++-- crates/router/src/connector/nexinets.rs | 20 +++--- crates/router/src/connector/nmi.rs | 28 ++++---- crates/router/src/connector/noon.rs | 22 +++---- crates/router/src/connector/nuvei.rs | 48 +++++++------- crates/router/src/connector/opayo.rs | 14 ++-- crates/router/src/connector/opennode.rs | 6 +- crates/router/src/connector/payeezy.rs | 20 +++--- crates/router/src/connector/payme.rs | 48 +++++++------- crates/router/src/connector/paypal.rs | 28 ++++---- crates/router/src/connector/payu.rs | 18 ++--- crates/router/src/connector/powertranz.rs | 28 ++++---- crates/router/src/connector/prophetpay.rs | 37 ++++++----- crates/router/src/connector/rapyd.rs | 18 ++--- crates/router/src/connector/shift4.rs | 20 +++--- crates/router/src/connector/square.rs | 16 ++--- crates/router/src/connector/stax.rs | 24 +++---- crates/router/src/connector/stripe.rs | 48 +++++++------- crates/router/src/connector/trustpay.rs | 18 ++--- crates/router/src/connector/tsys.rs | 34 +++++----- crates/router/src/connector/volt.rs | 20 +++--- crates/router/src/connector/wise.rs | 16 ++--- crates/router/src/connector/worldline.rs | 12 ++-- crates/router/src/connector/worldpay.rs | 12 ++-- crates/router/src/connector/zen.rs | 12 ++-- crates/router/src/services/api.rs | 3 +- 53 files changed, 586 insertions(+), 590 deletions(-) diff --git a/crates/common_utils/src/request.rs b/crates/common_utils/src/request.rs index 64bce8649d97..36997f773ff0 100644 --- a/crates/common_utils/src/request.rs +++ b/crates/common_utils/src/request.rs @@ -24,6 +24,7 @@ pub enum ContentType { Json, FormUrlEncoded, FormData, + Xml, } fn default_request_headers() -> [(String, Maskable); 1] { @@ -36,12 +37,28 @@ fn default_request_headers() -> [(String, Maskable); 1] { pub struct Request { pub url: String, pub headers: Headers, - pub payload: Option>, pub method: Method, - pub content_type: Option, pub certificate: Option, pub certificate_key: Option, - pub form_data: Option, + pub body: Option, +} + +impl std::fmt::Debug for RequestContent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::Json(_) => "JsonRequestBody", + Self::FormUrlEncoded(_) => "FormUrlEncodedRequestBody", + Self::FormData(_) => "FormDataRequestBody", + Self::Xml(_) => "XmlRequestBody", + }) + } +} + +pub enum RequestContent { + Json(Box), + FormUrlEncoded(Box), + FormData(reqwest::multipart::Form), + Xml(Box), } impl Request { @@ -50,16 +67,14 @@ impl Request { method, url: String::from(url), headers: std::collections::HashSet::new(), - payload: None, - content_type: None, certificate: None, certificate_key: None, - form_data: None, + body: None, } } - pub fn set_body(&mut self, body: String) { - self.payload = Some(body.into()); + pub fn set_body>(&mut self, body: T) { + self.body.replace(body.into()); } pub fn add_default_headers(&mut self) { @@ -70,10 +85,6 @@ impl Request { self.headers.insert((String::from(header), value)); } - pub fn add_content_type(&mut self, content_type: ContentType) { - self.content_type = Some(content_type); - } - pub fn add_certificate(&mut self, certificate: Option) { self.certificate = certificate; } @@ -81,22 +92,16 @@ impl Request { pub fn add_certificate_key(&mut self, certificate_key: Option) { self.certificate = certificate_key; } - - pub fn set_form_data(&mut self, form_data: reqwest::multipart::Form) { - self.form_data = Some(form_data); - } } #[derive(Debug)] pub struct RequestBuilder { pub url: String, pub headers: Headers, - pub payload: Option>, pub method: Method, - pub content_type: Option, pub certificate: Option, pub certificate_key: Option, - pub form_data: Option, + pub body: Option, } impl RequestBuilder { @@ -105,11 +110,9 @@ impl RequestBuilder { method: Method::Get, url: String::with_capacity(1024), headers: std::collections::HashSet::new(), - payload: None, - content_type: None, certificate: None, certificate_key: None, - form_data: None, + body: None, } } @@ -139,18 +142,8 @@ impl RequestBuilder { self } - pub fn form_data(mut self, form_data: Option) -> Self { - self.form_data = form_data; - self - } - - pub fn body(mut self, option_body: Option) -> Self { - self.payload = option_body.map(RequestBody::get_inner_value); - self - } - - pub fn content_type(mut self, content_type: ContentType) -> Self { - self.content_type = Some(content_type); + pub fn set_body>(mut self, body: T) -> Self { + self.body.replace(body.into()); self } @@ -169,11 +162,9 @@ impl RequestBuilder { method: self.method, url: self.url, headers: self.headers, - payload: self.payload, - content_type: self.content_type, certificate: self.certificate, certificate_key: self.certificate_key, - form_data: self.form_data, + body: self.body, } } } diff --git a/crates/router/src/connector/aci.rs b/crates/router/src/connector/aci.rs index f51c91f441df..f41f442875f5 100644 --- a/crates/router/src/connector/aci.rs +++ b/crates/router/src/connector/aci.rs @@ -2,6 +2,7 @@ mod result_codes; pub mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as aci; @@ -283,7 +284,7 @@ impl &self, req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { // encode only for for urlencoded things. let connector_router_data = aci::AciRouterData::try_from(( &self.get_currency_unit(), @@ -298,7 +299,7 @@ impl ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(aci_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -392,14 +393,14 @@ impl &self, req: &types::PaymentsCancelRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = aci::AciCancelRequest::try_from(req)?; let aci_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(aci_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -488,7 +489,7 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = aci::AciRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -501,7 +502,7 @@ impl services::ConnectorIntegration::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(body)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/adyen.rs b/crates/router/src/connector/adyen.rs index 676f15d2f564..a6dbc3f51c71 100644 --- a/crates/router/src/connector/adyen.rs +++ b/crates/router/src/connector/adyen.rs @@ -173,7 +173,7 @@ impl &self, req: &types::SetupMandateRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let authorize_req = types::PaymentsAuthorizeRouterData::from(( req, types::PaymentsAuthorizeData::from(req), @@ -191,7 +191,7 @@ impl utils::Encode::>::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(adyen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -310,7 +310,7 @@ impl &self, req: &types::PaymentsCaptureRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = adyen::AdyenRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -323,7 +323,7 @@ impl utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(adyen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -405,7 +405,7 @@ impl &self, req: &types::RouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { // Adyen doesn't support PSync flow. We use PSync flow to fetch payment details, // specifically the redirect URL that takes the user to their Payment page. In non-redirection flows, // we rely on webhooks to obtain the payment status since there is no encoded data available. @@ -642,7 +642,7 @@ impl &self, req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = adyen::AdyenRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -765,7 +765,7 @@ impl &self, req: &types::PaymentsBalanceRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = adyen::AdyenBalanceRequest::try_from(req)?; let adyen_req = types::RequestBody::log_and_get_request_body( @@ -773,7 +773,7 @@ impl utils::Encode::>::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(adyen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -861,7 +861,7 @@ impl &self, req: &types::PaymentsCancelRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = adyen::AdyenCancelRequest::try_from(req)?; let adyen_req = types::RequestBody::log_and_get_request_body( @@ -869,7 +869,7 @@ impl utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(adyen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -974,14 +974,14 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = adyen::AdyenPayoutCancelRequest::try_from(req)?; let adyen_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(adyen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -1062,7 +1062,7 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = adyen::AdyenRouterData::try_from(( &self.get_currency_unit(), req.request.destination_currency, @@ -1075,7 +1075,7 @@ impl services::ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(adyen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -1157,7 +1157,7 @@ impl &self, req: &types::PayoutsRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = adyen::AdyenRouterData::try_from(( &self.get_currency_unit(), req.request.destination_currency, @@ -1170,7 +1170,7 @@ impl utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(adyen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -1269,7 +1269,7 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = adyen::AdyenRouterData::try_from(( &self.get_currency_unit(), req.request.destination_currency, @@ -1282,7 +1282,7 @@ impl services::ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(adyen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -1370,7 +1370,7 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = adyen::AdyenRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -1384,7 +1384,7 @@ impl services::ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(adyen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/airwallex.rs b/crates/router/src/connector/airwallex.rs index 33e3dae72871..2daec2361cf9 100644 --- a/crates/router/src/connector/airwallex.rs +++ b/crates/router/src/connector/airwallex.rs @@ -256,14 +256,14 @@ impl &self, req: &types::PaymentsInitRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let req_obj = airwallex::AirwallexIntentRequest::try_from(req)?; let req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + Ok(RequestContent::Json(Box::new(req_obj))) } fn build_request( @@ -379,7 +379,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = airwallex::AirwallexRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -392,7 +392,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(airwallex_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -556,7 +556,7 @@ impl &self, req: &types::PaymentsCompleteAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let req_obj = airwallex::AirwallexCompleteRequest::try_from(req)?; let req = types::RequestBody::log_and_get_request_body( @@ -564,7 +564,7 @@ impl utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + Ok(RequestContent::Json(Box::new(req_obj))) } fn build_request( &self, @@ -645,7 +645,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = airwallex::AirwallexPaymentsCaptureRequest::try_from(req)?; let airwallex_req = types::RequestBody::log_and_get_request_body( @@ -654,7 +654,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = airwallex::AirwallexPaymentsCancelRequest::try_from(req)?; let airwallex_req = types::RequestBody::log_and_get_request_body( @@ -754,7 +754,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(airwallex_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn handle_response( &self, @@ -836,7 +836,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = airwallex::AirwallexRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -849,7 +849,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(airwallex_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/authorizedotnet.rs b/crates/router/src/connector/authorizedotnet.rs index f3cdf0415b91..5682ed4c5b3e 100644 --- a/crates/router/src/connector/authorizedotnet.rs +++ b/crates/router/src/connector/authorizedotnet.rs @@ -146,7 +146,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = authorizedotnet::AuthorizedotnetRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -161,7 +161,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(authorizedotnet_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -244,7 +244,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = authorizedotnet::AuthorizedotnetCreateSyncRequest::try_from(req)?; let sync_request = types::RequestBody::log_and_get_request_body( &connector_req, @@ -332,7 +332,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = authorizedotnet::AuthorizedotnetRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -347,7 +347,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(authorizedotnet_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -434,7 +434,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = authorizedotnet::CancelOrCaptureTransactionRequest::try_from(req)?; let authorizedotnet_req = types::RequestBody::log_and_get_request_body( @@ -442,7 +442,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(authorizedotnet_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -526,7 +526,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = authorizedotnet::AuthorizedotnetRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -540,7 +540,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(authorizedotnet_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -622,7 +622,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = authorizedotnet::AuthorizedotnetRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -720,7 +720,7 @@ impl &self, req: &types::PaymentsCompleteAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = authorizedotnet::AuthorizedotnetRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -735,7 +735,7 @@ impl utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(authorizedotnet_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/bambora.rs b/crates/router/src/connector/bambora.rs index ff6fdcb46769..bd1b3c48a2b0 100644 --- a/crates/router/src/connector/bambora.rs +++ b/crates/router/src/connector/bambora.rs @@ -174,15 +174,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let request = bambora::BamboraPaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = bambora::BamboraPaymentsRequest::try_from(req)?; let bambora_req = types::RequestBody::log_and_get_request_body( &request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bambora_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -347,14 +347,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = bambora::BamboraPaymentsCaptureRequest::try_from(req)?; let bambora_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bambora_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -441,15 +441,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let request = bambora::BamboraPaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = bambora::BamboraPaymentsRequest::try_from(req)?; let bambora_req = types::RequestBody::log_and_get_request_body( &request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bambora_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -541,14 +541,14 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = bambora::BamboraRefundRequest::try_from(req)?; let bambora_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bambora_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -752,15 +752,15 @@ impl &self, req: &types::PaymentsCompleteAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = bambora::BamboraThreedsContinueRequest::try_from(&req.request)?; + ) -> CustomResult { + let connector_req = bambora::BamboraThreedsContinueRequest::try_from(&req.request)?; let bambora_req = types::RequestBody::log_and_get_request_body( &request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bambora_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/bankofamerica.rs b/crates/router/src/connector/bankofamerica.rs index b6e19fa0d296..399acc1e4d28 100644 --- a/crates/router/src/connector/bankofamerica.rs +++ b/crates/router/src/connector/bankofamerica.rs @@ -302,21 +302,21 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let connector_request = + let connector_req = bankofamerica::BankOfAmericaPaymentsRequest::try_from(&connector_router_data)?; let bankofamerica_payments_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bankofamerica_payments_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -470,21 +470,21 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount_to_capture, req, ))?; - let connector_request = + let connector_req = bankofamerica::BankOfAmericaCaptureRequest::try_from(&connector_router_data)?; let bankofamerica_capture_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bankofamerica_capture_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -562,7 +562,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from(( &self.get_currency_unit(), req.request @@ -577,7 +577,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bankofamerica_void_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -661,20 +661,21 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = bankofamerica::BankOfAmericaRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = bankofamerica::BankOfAmericaRefundRequest::try_from(&connector_router_data)?; + let connector_req = + bankofamerica::BankOfAmericaRefundRequest::try_from(&connector_router_data)?; let bankofamerica_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bankofamerica_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/bitpay.rs b/crates/router/src/connector/bitpay.rs index 856d0a9ec9d7..9079fd977a56 100644 --- a/crates/router/src/connector/bitpay.rs +++ b/crates/router/src/connector/bitpay.rs @@ -174,21 +174,21 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = bitpay::BitpayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = bitpay::BitpayPaymentsRequest::try_from(&connector_router_data)?; + let connector_req = bitpay::BitpayPaymentsRequest::try_from(&connector_router_data)?; let bitpay_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bitpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/bluesnap.rs b/crates/router/src/connector/bluesnap.rs index d1aa1fa25ee6..b67e54c8e501 100644 --- a/crates/router/src/connector/bluesnap.rs +++ b/crates/router/src/connector/bluesnap.rs @@ -264,14 +264,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = bluesnap::BluesnapVoidRequest::try_from(req)?; let bluesnap_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bluesnap_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -441,7 +441,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = bluesnap::BluesnapRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -454,7 +454,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bluesnap_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -536,14 +536,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = bluesnap::BluesnapCreateWalletToken::try_from(req)?; let bluesnap_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bluesnap_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -632,7 +632,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = bluesnap::BluesnapRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -648,7 +648,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bluesnap_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } _ => { let connector_req = @@ -658,7 +658,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bluesnap_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } } } @@ -770,7 +770,7 @@ impl &self, req: &types::PaymentsCompleteAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = bluesnap::BluesnapRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -784,7 +784,7 @@ impl utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bluesnap_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -869,7 +869,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = bluesnap::BluesnapRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -882,7 +882,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(bluesnap_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/boku.rs b/crates/router/src/connector/boku.rs index 87e8fd0eb96a..76b435de5a38 100644 --- a/crates/router/src/connector/boku.rs +++ b/crates/router/src/connector/boku.rs @@ -208,14 +208,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = boku::BokuPaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = boku::BokuPaymentsRequest::try_from(req)?; let boku_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_xml, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(boku_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -301,14 +301,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = boku::BokuPsyncRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = boku::BokuPsyncRequest::try_from(req)?; let boku_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_xml, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(boku_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -385,7 +385,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) } @@ -473,14 +473,14 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = boku::BokuRefundRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = boku::BokuRefundRequest::try_from(req)?; let boku_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_xml, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(boku_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -556,14 +556,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = boku::BokuRsyncRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = boku::BokuRsyncRequest::try_from(req)?; let boku_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_xml, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(boku_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/braintree.rs b/crates/router/src/connector/braintree.rs index 99f6b9955d57..71f98ca92086 100644 --- a/crates/router/src/connector/braintree.rs +++ b/crates/router/src/connector/braintree.rs @@ -263,14 +263,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_request = braintree::BraintreeSessionRequest::try_from(req)?; let braintree_session_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(braintree_session_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn handle_response( @@ -328,7 +328,7 @@ impl &self, req: &types::TokenizationRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_request = braintree_graphql_transformers::BraintreeTokenRequest::try_from(req)?; @@ -337,7 +337,7 @@ impl utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(braintree_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -446,7 +446,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_api_version = &req.connector_api_version.clone(); let connector_router_data = braintree_graphql_transformers::BraintreeRouterData::try_from(( @@ -467,7 +467,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(braintree_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } false => Err(errors::ConnectorError::NotImplemented( "get_request_body method".to_string(), @@ -599,7 +599,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_api_version = &req.connector_api_version; match self.is_braintree_graphql_version(connector_api_version) { true => { @@ -611,7 +611,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(braintree_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } false => Ok(None), } @@ -775,7 +775,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_api_version = &req.connector_api_version; let connector_router_data = braintree_graphql_transformers::BraintreeRouterData::try_from(( @@ -948,7 +948,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_api_version = &req.connector_api_version; match self.is_braintree_graphql_version(connector_api_version) { true => { @@ -959,7 +959,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(braintree_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } false => Ok(None), } @@ -1078,7 +1078,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_api_version = &req.connector_api_version; let connector_router_data = braintree_graphql_transformers::BraintreeRouterData::try_from(( @@ -1215,7 +1215,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_api_version = &req.connector_api_version; match self.is_braintree_graphql_version(connector_api_version) { true => { @@ -1609,7 +1609,7 @@ impl &self, req: &types::PaymentsCompleteAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = braintree_graphql_transformers::BraintreeRouterData::try_from(( &self.get_currency_unit(), diff --git a/crates/router/src/connector/cashtocode.rs b/crates/router/src/connector/cashtocode.rs index a8d7d6d80504..2cbd4f762e7e 100644 --- a/crates/router/src/connector/cashtocode.rs +++ b/crates/router/src/connector/cashtocode.rs @@ -204,14 +204,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = cashtocode::CashtocodePaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = cashtocode::CashtocodePaymentsRequest::try_from(req)?; let cashtocode_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(cashtocode_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/checkout.rs b/crates/router/src/connector/checkout.rs index ca2556544f90..9ed2b0c9bb32 100644 --- a/crates/router/src/connector/checkout.rs +++ b/crates/router/src/connector/checkout.rs @@ -209,14 +209,14 @@ impl &self, req: &types::TokenizationRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = checkout::TokenRequest::try_from(req)?; let checkout_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(checkout_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -317,7 +317,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = checkout::CheckoutRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -330,7 +330,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(checkout_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -507,7 +507,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = checkout::CheckoutRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -520,7 +520,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(checkout_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -601,14 +601,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = checkout::PaymentVoidRequest::try_from(req)?; let checkout_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(checkout_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -691,7 +691,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = checkout::CheckoutRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -973,7 +973,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { let checkout_req = transformers::construct_file_upload_request(req.clone())?; - Ok(Some(checkout_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -1063,7 +1063,7 @@ impl &self, req: &types::SubmitEvidenceRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let checkout_req = checkout::Evidence::try_from(req)?; let checkout_req_string = types::RequestBody::log_and_get_request_body( &checkout_req, diff --git a/crates/router/src/connector/coinbase.rs b/crates/router/src/connector/coinbase.rs index 9c0a06a52c90..1c13b61ef30b 100644 --- a/crates/router/src/connector/coinbase.rs +++ b/crates/router/src/connector/coinbase.rs @@ -185,14 +185,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let connector_request = coinbase::CoinbasePaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = coinbase::CoinbasePaymentsRequest::try_from(req)?; let coinbase_payment_request = types::RequestBody::log_and_get_request_body( &connector_request, Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(coinbase_payment_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/cryptopay.rs b/crates/router/src/connector/cryptopay.rs index 417a36145b92..3ae7839b4bd5 100644 --- a/crates/router/src/connector/cryptopay.rs +++ b/crates/router/src/connector/cryptopay.rs @@ -218,21 +218,20 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = cryptopay::CryptopayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let connector_request = - cryptopay::CryptopayPaymentsRequest::try_from(&connector_router_data)?; + let connector_req = cryptopay::CryptopayPaymentsRequest::try_from(&connector_router_data)?; let cryptopay_req = types::RequestBody::log_and_get_request_body( &connector_request, Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(cryptopay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/cybersource.rs b/crates/router/src/connector/cybersource.rs index ce283b12b798..95c9197fe915 100644 --- a/crates/router/src/connector/cybersource.rs +++ b/crates/router/src/connector/cybersource.rs @@ -273,14 +273,14 @@ impl &self, req: &types::SetupMandateRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = cybersource::CybersourceZeroMandateRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = cybersource::CybersourceZeroMandateRequest::try_from(req)?; let cybersource_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(cybersource_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -373,21 +373,21 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = cybersource::CybersourceRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount_to_capture, req, ))?; - let connector_request = + let connector_req = cybersource::CybersourcePaymentsCaptureRequest::try_from(&connector_router_data)?; let cybersource_payments_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(cybersource_payments_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -479,7 +479,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { Ok(Some( types::RequestBody::log_and_get_request_body("{}".to_string(), Ok) .change_context(errors::ConnectorError::RequestEncodingFailed)?, @@ -558,21 +558,21 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = cybersource::CybersourceRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let connector_request = + let connector_req = cybersource::CybersourcePaymentsRequest::try_from(&connector_router_data)?; let cybersource_payments_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(cybersource_payments_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -657,7 +657,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { Ok(Some( types::RequestBody::log_and_get_request_body("{}".to_string(), Ok) .change_context(errors::ConnectorError::RequestEncodingFailed)?, @@ -745,14 +745,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = cybersource::CybersourceRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let connector_request = + let connector_req = cybersource::CybersourceRefundRequest::try_from(&connector_router_data)?; let cybersource_refund_request = types::RequestBody::log_and_get_request_body( &connector_request, diff --git a/crates/router/src/connector/dlocal.rs b/crates/router/src/connector/dlocal.rs index 4ae3a292fdae..9bd1d52db91e 100644 --- a/crates/router/src/connector/dlocal.rs +++ b/crates/router/src/connector/dlocal.rs @@ -212,20 +212,20 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = dlocal::DlocalRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let connector_request = dlocal::DlocalPaymentsRequest::try_from(&connector_router_data)?; + let connector_req = dlocal::DlocalPaymentsRequest::try_from(&connector_router_data)?; let dlocal_payments_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(dlocal_payments_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -373,14 +373,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let connector_request = dlocal::DlocalPaymentsCaptureRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = dlocal::DlocalPaymentsCaptureRequest::try_from(req)?; let dlocal_payments_capture_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(dlocal_payments_capture_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -525,20 +525,20 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = dlocal::DlocalRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let connector_request = dlocal::DlocalRefundRequest::try_from(&connector_router_data)?; + let connector_req = dlocal::DlocalRefundRequest::try_from(&connector_router_data)?; let dlocal_refund_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(dlocal_refund_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/dummyconnector.rs b/crates/router/src/connector/dummyconnector.rs index 9edcd957ff09..94f4f0158481 100644 --- a/crates/router/src/connector/dummyconnector.rs +++ b/crates/router/src/connector/dummyconnector.rs @@ -189,14 +189,14 @@ impl &self, req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let connector_request = transformers::DummyConnectorPaymentsRequest::::try_from(req)?; + ) -> CustomResult { + let connector_req = transformers::DummyConnectorPaymentsRequest::::try_from(req)?; let dummmy_payments_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::>::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(dummmy_payments_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -352,7 +352,7 @@ impl &self, _req: &types::PaymentsCaptureRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) } @@ -435,14 +435,14 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let connector_request = transformers::DummyConnectorRefundRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = transformers::DummyConnectorRefundRequest::try_from(req)?; let dummmy_refund_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(dummmy_refund_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/fiserv.rs b/crates/router/src/connector/fiserv.rs index 2bdb7177d941..b8db2755c6ea 100644 --- a/crates/router/src/connector/fiserv.rs +++ b/crates/router/src/connector/fiserv.rs @@ -247,8 +247,8 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let connector_request = fiserv::FiservCancelRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = fiserv::FiservCancelRequest::try_from(req)?; let fiserv_payments_cancel_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, @@ -336,14 +336,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let connector_request = fiserv::FiservSyncRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = fiserv::FiservSyncRequest::try_from(req)?; let fiserv_payments_sync_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(fiserv_payments_sync_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -412,20 +412,20 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let router_obj = fiserv::FiservRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount_to_capture, req, ))?; - let connector_request = fiserv::FiservCaptureRequest::try_from(&router_obj)?; + let connector_req = fiserv::FiservCaptureRequest::try_from(&router_obj)?; let fiserv_payments_capture_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(fiserv_payments_capture_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -526,20 +526,20 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let router_obj = fiserv::FiservRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let connector_request = fiserv::FiservPaymentsRequest::try_from(&router_obj)?; + let connector_req = fiserv::FiservPaymentsRequest::try_from(&router_obj)?; let fiserv_payments_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(fiserv_payments_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -622,20 +622,20 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let router_obj = fiserv::FiservRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let connector_request = fiserv::FiservRefundRequest::try_from(&router_obj)?; + let connector_req = fiserv::FiservRefundRequest::try_from(&router_obj)?; let fiserv_refund_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(fiserv_refund_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -711,14 +711,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let connector_request = fiserv::FiservSyncRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = fiserv::FiservSyncRequest::try_from(req)?; let fiserv_sync_request = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(fiserv_sync_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/forte.rs b/crates/router/src/connector/forte.rs index 3aa7cee32878..b2ec83b3e9b3 100644 --- a/crates/router/src/connector/forte.rs +++ b/crates/router/src/connector/forte.rs @@ -202,14 +202,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = forte::FortePaymentsRequest::try_from(req)?; let forte_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(forte_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -361,14 +361,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = forte::ForteCaptureRequest::try_from(req)?; let forte_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(forte_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -447,14 +447,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = forte::ForteCancelRequest::try_from(req)?; let forte_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(forte_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -530,14 +530,14 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = forte::ForteRefundRequest::try_from(req)?; let forte_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(forte_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/globalpay.rs b/crates/router/src/connector/globalpay.rs index 26494d349b88..dcbdb3fe4db4 100644 --- a/crates/router/src/connector/globalpay.rs +++ b/crates/router/src/connector/globalpay.rs @@ -166,10 +166,10 @@ impl &self, _req: &types::PaymentsCompleteAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let globalpay_req = types::RequestBody::log_and_get_request_body("{}".to_string(), Ok) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(globalpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -275,14 +275,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = GlobalpayRefreshTokenRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = GlobalpayRefreshTokenRequest::try_from(req)?; let globalpay_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(globalpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn handle_response( @@ -398,14 +398,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = requests::GlobalpayCancelRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = requests::GlobalpayCancelRequest::try_from(req)?; let globalpay_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(globalpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn handle_response( @@ -549,14 +549,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = requests::GlobalpayCaptureRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = requests::GlobalpayCaptureRequest::try_from(req)?; let globalpay_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(globalpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -641,14 +641,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = GlobalpayPaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = GlobalpayPaymentsRequest::try_from(req)?; let globalpay_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(globalpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -734,14 +734,14 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = requests::GlobalpayRefundRequest::try_from(req)?; let globalpay_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(globalpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/globepay.rs b/crates/router/src/connector/globepay.rs index 9ebea6087f42..6464ec37fea0 100644 --- a/crates/router/src/connector/globepay.rs +++ b/crates/router/src/connector/globepay.rs @@ -189,14 +189,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = globepay::GlobepayPaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = globepay::GlobepayPaymentsRequest::try_from(req)?; let globepay_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(globepay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -368,14 +368,14 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = globepay::GlobepayRefundRequest::try_from(req)?; let globepay_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(globepay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/gocardless.rs b/crates/router/src/connector/gocardless.rs index d25357121b66..6e7dfd4a14e2 100644 --- a/crates/router/src/connector/gocardless.rs +++ b/crates/router/src/connector/gocardless.rs @@ -154,14 +154,14 @@ impl &self, req: &types::ConnectorCustomerRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = gocardless::GocardlessCustomerRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = gocardless::GocardlessCustomerRequest::try_from(req)?; let gocardless_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(gocardless_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -252,14 +252,14 @@ impl &self, req: &types::TokenizationRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = gocardless::GocardlessBankAccountRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = gocardless::GocardlessBankAccountRequest::try_from(req)?; let gocardless_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(gocardless_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -373,14 +373,14 @@ impl &self, req: &types::SetupMandateRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = gocardless::GocardlessMandateRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = gocardless::GocardlessMandateRequest::try_from(req)?; let gocardless_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(gocardless_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -457,20 +457,21 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = gocardless::GocardlessRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = gocardless::GocardlessPaymentsRequest::try_from(&connector_router_data)?; + let connector_req = + gocardless::GocardlessPaymentsRequest::try_from(&connector_router_data)?; let gocardless_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(gocardless_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -625,20 +626,20 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = gocardless::GocardlessRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = gocardless::GocardlessRefundRequest::try_from(&connector_router_data)?; + let connector_req = gocardless::GocardlessRefundRequest::try_from(&connector_router_data)?; let gocardless_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(gocardless_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/helcim.rs b/crates/router/src/connector/helcim.rs index a1781a92ddf5..d123497581d5 100644 --- a/crates/router/src/connector/helcim.rs +++ b/crates/router/src/connector/helcim.rs @@ -194,7 +194,7 @@ impl &self, req: &types::SetupMandateRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = helcim::HelcimVerifyRequest::try_from(req)?; let helcim_req = types::RequestBody::log_and_get_request_body( @@ -202,7 +202,7 @@ impl utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(helcim_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -274,20 +274,20 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { 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 connector_req = 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, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(helcim_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -448,7 +448,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = helcim::HelcimRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -461,7 +461,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(helcim_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -535,14 +535,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = helcim::HelcimVoidRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = 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)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -613,20 +613,20 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { 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 connector_req = 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, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(helcim_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/iatapay.rs b/crates/router/src/connector/iatapay.rs index ba4b95f43808..9992bc681410 100644 --- a/crates/router/src/connector/iatapay.rs +++ b/crates/router/src/connector/iatapay.rs @@ -179,14 +179,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = iatapay::IatapayAuthUpdateRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = iatapay::IatapayAuthUpdateRequest::try_from(req)?; let iatapay_req = types::RequestBody::log_and_get_request_body( &req_obj, Encode::::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(iatapay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -280,20 +280,20 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = iatapay::IatapayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = iatapay::IatapayPaymentsRequest::try_from(&connector_router_data)?; + let connector_req = iatapay::IatapayPaymentsRequest::try_from(&connector_router_data)?; let iatapay_req = types::RequestBody::log_and_get_request_body( &req_obj, Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(iatapay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -469,20 +469,20 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = iatapay::IatapayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.payment_amount, req, ))?; - let req_obj = iatapay::IatapayRefundRequest::try_from(&connector_router_data)?; + let connector_req = iatapay::IatapayRefundRequest::try_from(&connector_router_data)?; let iatapay_req = types::RequestBody::log_and_get_request_body( &req_obj, Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(iatapay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/klarna.rs b/crates/router/src/connector/klarna.rs index f34414e737ff..1556b8ebcbd9 100644 --- a/crates/router/src/connector/klarna.rs +++ b/crates/router/src/connector/klarna.rs @@ -155,7 +155,7 @@ impl &self, req: &types::PaymentsSessionRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = klarna::KlarnaSessionRequest::try_from(req)?; // encode only for for urlencoded things. let klarna_req = types::RequestBody::log_and_get_request_body( @@ -163,7 +163,7 @@ impl utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(klarna_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -415,7 +415,7 @@ impl &self, req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = klarna::KlarnaRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -428,7 +428,7 @@ impl utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(klarna_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/mollie.rs b/crates/router/src/connector/mollie.rs index 76deb0b2be88..49a5fcd170f0 100644 --- a/crates/router/src/connector/mollie.rs +++ b/crates/router/src/connector/mollie.rs @@ -151,14 +151,14 @@ impl &self, req: &types::TokenizationRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = mollie::MollieCardTokenRequest::try_from(req)?; let mollie_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(mollie_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -237,20 +237,20 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let router_obj = mollie::MollieRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = mollie::MolliePaymentsRequest::try_from(&router_obj)?; + let connector_req = mollie::MolliePaymentsRequest::try_from(&router_obj)?; let mollie_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(mollie_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -434,20 +434,20 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let router_obj = mollie::MollieRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = mollie::MollieRefundRequest::try_from(&router_obj)?; + let connector_req = mollie::MollieRefundRequest::try_from(&router_obj)?; let mollie_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(mollie_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/multisafepay.rs b/crates/router/src/connector/multisafepay.rs index 1f1099af0e71..2ebfdc18c94e 100644 --- a/crates/router/src/connector/multisafepay.rs +++ b/crates/router/src/connector/multisafepay.rs @@ -263,20 +263,21 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = multisafepay::MultisafepayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = multisafepay::MultisafepayPaymentsRequest::try_from(&connector_router_data)?; + let connector_req = + multisafepay::MultisafepayPaymentsRequest::try_from(&connector_router_data)?; let multisafepay_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(multisafepay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -367,21 +368,21 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = multisafepay::MultisafepayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = multisafepay::MultisafepayRefundRequest::try_from(&connector_req)?; + let connector_req = multisafepay::MultisafepayRefundRequest::try_from(&connector_req)?; let multisafepay_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(multisafepay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/nexinets.rs b/crates/router/src/connector/nexinets.rs index a67a29d74ffe..3d4af36818c7 100644 --- a/crates/router/src/connector/nexinets.rs +++ b/crates/router/src/connector/nexinets.rs @@ -201,14 +201,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = nexinets::NexinetsPaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = nexinets::NexinetsPaymentsRequest::try_from(req)?; let nexinets_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nexinets_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -366,14 +366,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = nexinets::NexinetsCaptureOrVoidRequest::try_from(req)?; let nexinets_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nexinets_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -454,14 +454,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = nexinets::NexinetsCaptureOrVoidRequest::try_from(req)?; let nexinets_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nexinets_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -539,14 +539,14 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = nexinets::NexinetsRefundRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = nexinets::NexinetsRefundRequest::try_from(req)?; let nexinets_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nexinets_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/nmi.rs b/crates/router/src/connector/nmi.rs index eaede225d38f..d2e00bd528a5 100644 --- a/crates/router/src/connector/nmi.rs +++ b/crates/router/src/connector/nmi.rs @@ -144,14 +144,14 @@ impl &self, req: &types::SetupMandateRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = nmi::NmiPaymentsRequest::try_from(req)?; let nmi_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nmi_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -217,7 +217,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = nmi::NmiRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -230,7 +230,7 @@ impl ConnectorIntegration::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nmi_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -300,14 +300,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = nmi::NmiSyncRequest::try_from(req)?; let nmi_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nmi_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -370,7 +370,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = nmi::NmiRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -383,7 +383,7 @@ impl ConnectorIntegration::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nmi_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -451,14 +451,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = nmi::NmiCancelRequest::try_from(req)?; let nmi_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nmi_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -522,7 +522,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = nmi::NmiRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -535,7 +535,7 @@ impl ConnectorIntegration::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nmi_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -601,14 +601,14 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = nmi::NmiSyncRequest::try_from(req)?; let nmi_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(nmi_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/noon.rs b/crates/router/src/connector/noon.rs index b6ed231e5b50..9b22ce3dd6c4 100644 --- a/crates/router/src/connector/noon.rs +++ b/crates/router/src/connector/noon.rs @@ -211,14 +211,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = noon::NoonPaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = noon::NoonPaymentsRequest::try_from(req)?; let noon_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(noon_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -360,14 +360,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = noon::NoonPaymentsActionRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = noon::NoonPaymentsActionRequest::try_from(req)?; let noon_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(noon_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -440,14 +440,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = noon::NoonPaymentsCancelRequest::try_from(req)?; let noon_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(noon_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -517,14 +517,14 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = noon::NoonPaymentsActionRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = noon::NoonPaymentsActionRequest::try_from(req)?; let noon_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(noon_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/nuvei.rs b/crates/router/src/connector/nuvei.rs index 7a9f3af37f0c..c7b1593a19f8 100644 --- a/crates/router/src/connector/nuvei.rs +++ b/crates/router/src/connector/nuvei.rs @@ -150,16 +150,16 @@ impl &self, req: &types::PaymentsCompleteAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let meta: nuvei::NuveiMeta = utils::to_connector_meta(req.request.connector_meta.clone())?; - let req_obj = nuvei::NuveiPaymentsRequest::try_from((req, meta.session_token))?; + let connector_req = nuvei::NuveiPaymentsRequest::try_from((req, meta.session_token))?; let req = types::RequestBody::log_and_get_request_body( &req_obj, common_utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -237,14 +237,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = nuvei::NuveiPaymentFlowRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = nuvei::NuveiPaymentFlowRequest::try_from(req)?; let req = types::RequestBody::log_and_get_request_body( &req_obj, common_utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -324,14 +324,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = nuvei::NuveiPaymentSyncRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = nuvei::NuveiPaymentSyncRequest::try_from(req)?; let req = types::RequestBody::log_and_get_request_body( &req_obj, common_utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -406,14 +406,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = nuvei::NuveiPaymentFlowRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = nuvei::NuveiPaymentFlowRequest::try_from(req)?; let req = types::RequestBody::log_and_get_request_body( &req_obj, common_utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -566,15 +566,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = nuvei::NuveiPaymentsRequest::try_from((req, req.get_session_token()?))?; + ) -> CustomResult { + let connector_req = nuvei::NuveiPaymentsRequest::try_from((req, req.get_session_token()?))?; let req = types::RequestBody::log_and_get_request_body( &req_obj, common_utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -658,14 +658,14 @@ impl &self, req: &types::PaymentsAuthorizeSessionTokenRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = nuvei::NuveiSessionRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = nuvei::NuveiSessionRequest::try_from(req)?; let req = types::RequestBody::log_and_get_request_body( &req_obj, common_utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -742,15 +742,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = nuvei::NuveiPaymentsRequest::try_from((req, req.get_session_token()?))?; + ) -> CustomResult { + let connector_req = nuvei::NuveiPaymentsRequest::try_from((req, req.get_session_token()?))?; let req = types::RequestBody::log_and_get_request_body( &req_obj, common_utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -824,14 +824,14 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = nuvei::NuveiPaymentFlowRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = nuvei::NuveiPaymentFlowRequest::try_from(req)?; let req = types::RequestBody::log_and_get_request_body( &req_obj, common_utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/opayo.rs b/crates/router/src/connector/opayo.rs index ba0fb2046b7c..c10471d23150 100644 --- a/crates/router/src/connector/opayo.rs +++ b/crates/router/src/connector/opayo.rs @@ -174,14 +174,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = opayo::OpayoPaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = opayo::OpayoPaymentsRequest::try_from(req)?; let opayo_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(opayo_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -319,7 +319,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) } @@ -397,15 +397,15 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = opayo::OpayoRefundRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = opayo::OpayoRefundRequest::try_from(req)?; let opayo_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(opayo_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/opennode.rs b/crates/router/src/connector/opennode.rs index 41d1e6c3d88c..6b6db1f6e3cc 100644 --- a/crates/router/src/connector/opennode.rs +++ b/crates/router/src/connector/opennode.rs @@ -174,20 +174,20 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = opennode::OpennodeRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = opennode::OpennodePaymentsRequest::try_from(&connector_router_data)?; + let connector_req = opennode::OpennodePaymentsRequest::try_from(&connector_router_data)?; let opennode_req = types::RequestBody::log_and_get_request_body( &req_obj, Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(opennode_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/payeezy.rs b/crates/router/src/connector/payeezy.rs index 33a8ec65152e..343fde6aa040 100644 --- a/crates/router/src/connector/payeezy.rs +++ b/crates/router/src/connector/payeezy.rs @@ -201,7 +201,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = payeezy::PayeezyCaptureOrVoidRequest::try_from(req)?; let payeezy_req = types::RequestBody::log_and_get_request_body( &connector_req, @@ -300,21 +300,21 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let router_obj = payeezy::PayeezyRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount_to_capture, req, ))?; - let req_obj = payeezy::PayeezyCaptureOrVoidRequest::try_from(&router_obj)?; + let connector_req = payeezy::PayeezyCaptureOrVoidRequest::try_from(&router_obj)?; let payeezy_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payeezy_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -398,21 +398,21 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let router_obj = payeezy::PayeezyRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = payeezy::PayeezyPaymentsRequest::try_from(&router_obj)?; + let connector_req = payeezy::PayeezyPaymentsRequest::try_from(&router_obj)?; let payeezy_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payeezy_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -497,20 +497,20 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let router_obj = payeezy::PayeezyRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = payeezy::PayeezyRefundRequest::try_from(&router_obj)?; + let connector_req = payeezy::PayeezyRefundRequest::try_from(&router_obj)?; let payeezy_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payeezy_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/payme.rs b/crates/router/src/connector/payme.rs index 1e67f8a9f350..c0c294d481c5 100644 --- a/crates/router/src/connector/payme.rs +++ b/crates/router/src/connector/payme.rs @@ -151,15 +151,15 @@ impl &self, req: &types::TokenizationRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = payme::CaptureBuyerRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = payme::CaptureBuyerRequest::try_from(req)?; let payme_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payme_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -257,18 +257,18 @@ impl &self, req: &types::PaymentsPreProcessingRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let amount = req.request.get_amount()?; let currency = req.request.get_currency()?; let connector_router_data = payme::PaymeRouterData::try_from((&self.get_currency_unit(), currency, amount, req))?; - let req_obj = payme::GenerateSaleRequest::try_from(&connector_router_data)?; + let connector_req = payme::GenerateSaleRequest::try_from(&connector_router_data)?; let payme_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payme_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -383,14 +383,14 @@ impl &self, req: &types::PaymentsCompleteAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = payme::Pay3dsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = payme::Pay3dsRequest::try_from(req)?; let payme_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payme_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, @@ -486,20 +486,20 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = payme::PaymeRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = payme::PaymePaymentRequest::try_from(&connector_router_data)?; + let connector_req = payme::PaymePaymentRequest::try_from(&connector_router_data)?; let payme_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payme_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -583,14 +583,14 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = payme::PaymeQuerySaleRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = payme::PaymeQuerySaleRequest::try_from(req)?; let payme_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payme_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -671,20 +671,20 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = payme::PaymeRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount_to_capture, req, ))?; - let req_obj = payme::PaymentCaptureRequest::try_from(&connector_router_data)?; + let connector_req = payme::PaymentCaptureRequest::try_from(&connector_router_data)?; let payme_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payme_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -780,20 +780,20 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = payme::PaymeRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = payme::PaymeRefundRequest::try_from(&connector_router_data)?; + let connector_req = payme::PaymeRefundRequest::try_from(&connector_router_data)?; let payme_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payme_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -872,14 +872,14 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = payme::PaymeQueryTransactionRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = payme::PaymeQueryTransactionRequest::try_from(req)?; let payme_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payme_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/paypal.rs b/crates/router/src/connector/paypal.rs index 0e8cff8c0569..d61c400ec191 100644 --- a/crates/router/src/connector/paypal.rs +++ b/crates/router/src/connector/paypal.rs @@ -318,15 +318,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = paypal::PaypalAuthUpdateRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = paypal::PaypalAuthUpdateRequest::try_from(req)?; let paypal_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(paypal_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -420,7 +420,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = paypal::PaypalRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -430,13 +430,13 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(paypal_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -730,7 +730,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = paypal::PaypalRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -743,7 +743,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(paypal_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -894,20 +894,20 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = paypal::PaypalRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = paypal::PaypalRefundRequest::try_from(&connector_router_data)?; + let connector_req = paypal::PaypalRefundRequest::try_from(&connector_router_data)?; let paypal_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(paypal_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -1095,14 +1095,14 @@ impl types::VerifyWebhookSourceResponseData, >, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = paypal::PaypalSourceVerificationRequest::try_from(&req.request)?; + ) -> CustomResult { + let connector_req = paypal::PaypalSourceVerificationRequest::try_from(&req.request)?; let paypal_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(paypal_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn handle_response( diff --git a/crates/router/src/connector/payu.rs b/crates/router/src/connector/payu.rs index 2868b5de0523..867042abe9cb 100644 --- a/crates/router/src/connector/payu.rs +++ b/crates/router/src/connector/payu.rs @@ -245,15 +245,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = payu::PayuAuthUpdateRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = payu::PayuAuthUpdateRequest::try_from(req)?; let payu_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payu_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -422,14 +422,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = payu::PayuPaymentsCaptureRequest::try_from(req)?; let payu_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payu_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -519,14 +519,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = payu::PayuPaymentsRequest::try_from(req)?; let payu_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payu_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -616,14 +616,14 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = payu::PayuRefundRequest::try_from(req)?; let payu_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(payu_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/powertranz.rs b/crates/router/src/connector/powertranz.rs index d24fd27f1052..3a576343db3e 100644 --- a/crates/router/src/connector/powertranz.rs +++ b/crates/router/src/connector/powertranz.rs @@ -195,14 +195,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = powertranz::PowertranzPaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = powertranz::PowertranzPaymentsRequest::try_from(req)?; let powertranz_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(powertranz_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -282,7 +282,7 @@ impl &self, req: &types::PaymentsCompleteAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let redirect_payload: powertranz::RedirectResponsePayload = req .request .get_redirect_response_payload()? @@ -294,7 +294,7 @@ impl Ok(spi_token.to_string()) }) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(powertranz_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -376,14 +376,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = powertranz::PowertranzBaseRequest::try_from(&req.request)?; + ) -> CustomResult { + let connector_req = powertranz::PowertranzBaseRequest::try_from(&req.request)?; let powertranz_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(powertranz_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -453,14 +453,14 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = powertranz::PowertranzBaseRequest::try_from(&req.request)?; + ) -> CustomResult { + let connector_req = powertranz::PowertranzBaseRequest::try_from(&req.request)?; let powertranz_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(powertranz_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn handle_response( @@ -532,14 +532,14 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = powertranz::PowertranzBaseRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = powertranz::PowertranzBaseRequest::try_from(req)?; let powertranz_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(powertranz_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/prophetpay.rs b/crates/router/src/connector/prophetpay.rs index efe87bcefd9f..f362d725f794 100644 --- a/crates/router/src/connector/prophetpay.rs +++ b/crates/router/src/connector/prophetpay.rs @@ -175,21 +175,21 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = prophetpay::ProphetpayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = prophetpay::ProphetpayTokenRequest::try_from(&connector_router_data)?; + let connector_req = prophetpay::ProphetpayTokenRequest::try_from(&connector_router_data)?; let prophetpay_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(prophetpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -276,21 +276,22 @@ impl &self, req: &types::PaymentsCompleteAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = prophetpay::ProphetpayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = prophetpay::ProphetpayCompleteRequest::try_from(&connector_router_data)?; + let connector_req = + prophetpay::ProphetpayCompleteRequest::try_from(&connector_router_data)?; let prophetpay_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(prophetpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -372,15 +373,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = prophetpay::ProphetpaySyncRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = prophetpay::ProphetpaySyncRequest::try_from(req)?; let prophetpay_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(prophetpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -463,15 +464,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = prophetpay::ProphetpayVoidRequest::try_from(req)?; + ) -> CustomResult { + let connector_req =prophetpay::ProphetpayVoidRequest::try_from(req)?; let prophetpay_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(prophetpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } */ @@ -539,20 +540,20 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = prophetpay::ProphetpayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = prophetpay::ProphetpayRefundRequest::try_from(&connector_router_data)?; + let connector_req = prophetpay::ProphetpayRefundRequest::try_from(&connector_router_data)?; let prophetpay_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(prophetpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -629,15 +630,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = prophetpay::ProphetpayRefundSyncRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = prophetpay::ProphetpayRefundSyncRequest::try_from(req)?; let prophetpay_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(prophetpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/rapyd.rs b/crates/router/src/connector/rapyd.rs index 91a538f9991b..595173e02837 100644 --- a/crates/router/src/connector/rapyd.rs +++ b/crates/router/src/connector/rapyd.rs @@ -184,20 +184,20 @@ impl &self, req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = rapyd::RapydRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = rapyd::RapydPaymentsRequest::try_from(&connector_router_data)?; + let connector_req = rapyd::RapydPaymentsRequest::try_from(&connector_router_data)?; let rapyd_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(rapyd_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -497,20 +497,20 @@ impl &self, req: &types::PaymentsCaptureRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = rapyd::RapydRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount_to_capture, req, ))?; - let req_obj = rapyd::CaptureRequest::try_from(&connector_router_data)?; + let connector_req = rapyd::CaptureRequest::try_from(&connector_router_data)?; let rapyd_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(rapyd_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -638,21 +638,21 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = rapyd::RapydRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = rapyd::RapydRefundRequest::try_from(&connector_router_data)?; + let connector_req = rapyd::RapydRefundRequest::try_from(&connector_router_data)?; let rapyd_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(rapyd_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/shift4.rs b/crates/router/src/connector/shift4.rs index 6f3a2b802014..034e222ac98d 100644 --- a/crates/router/src/connector/shift4.rs +++ b/crates/router/src/connector/shift4.rs @@ -187,14 +187,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = shift4::Shift4PaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = shift4::Shift4PaymentsRequest::try_from(req)?; let req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + Ok(RequestContent::Json(Box::new(connector_req))) } async fn execute_pretasks( @@ -478,14 +478,14 @@ impl &self, req: &types::PaymentsInitRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = shift4::Shift4PaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = shift4::Shift4PaymentsRequest::try_from(req)?; let req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -563,14 +563,14 @@ impl &self, req: &types::PaymentsCompleteAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = shift4::Shift4PaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = shift4::Shift4PaymentsRequest::try_from(req)?; let req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -644,7 +644,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = shift4::Shift4RefundRequest::try_from(req)?; let shift4_req = types::RequestBody::log_and_get_request_body( &connector_req, diff --git a/crates/router/src/connector/square.rs b/crates/router/src/connector/square.rs index d836285755d4..331fd33163f0 100644 --- a/crates/router/src/connector/square.rs +++ b/crates/router/src/connector/square.rs @@ -247,7 +247,7 @@ impl &self, req: &types::TokenizationRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_request = square::SquareTokenRequest::try_from(req)?; let square_req = types::RequestBody::log_and_get_request_body( @@ -255,7 +255,7 @@ impl utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(square_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -417,15 +417,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = square::SquarePaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = square::SquarePaymentsRequest::try_from(req)?; let square_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(square_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -710,14 +710,14 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = square::SquareRefundRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = square::SquareRefundRequest::try_from(req)?; let square_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(square_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/stax.rs b/crates/router/src/connector/stax.rs index 024211c8caaa..0559f45a189a 100644 --- a/crates/router/src/connector/stax.rs +++ b/crates/router/src/connector/stax.rs @@ -162,7 +162,7 @@ impl &self, req: &types::ConnectorCustomerRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_request = stax::StaxCustomerRequest::try_from(req)?; let stax_req = types::RequestBody::log_and_get_request_body( @@ -170,7 +170,7 @@ impl utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stax_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -254,7 +254,7 @@ impl &self, req: &types::TokenizationRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_request = stax::StaxTokenRequest::try_from(req)?; let stax_req = types::RequestBody::log_and_get_request_body( @@ -262,7 +262,7 @@ impl utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stax_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -358,21 +358,21 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = stax::StaxRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = stax::StaxPaymentsRequest::try_from(&connector_router_data)?; + let connector_req = stax::StaxPaymentsRequest::try_from(&connector_router_data)?; let stax_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stax_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -523,7 +523,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = stax::StaxRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -536,7 +536,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stax_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -686,20 +686,20 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = stax::StaxRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = stax::StaxRefundRequest::try_from(&connector_router_data)?; + let connector_req = stax::StaxRefundRequest::try_from(&connector_router_data)?; let stax_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stax_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index ccf843ec78d6..af6c63ac8d00 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -145,8 +145,8 @@ impl &self, req: &types::PaymentsPreProcessingRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req = stripe::StripeCreditTransferSourceRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = stripe::StripeCreditTransferSourceRequest::try_from(req)?; let pre_processing_request = types::RequestBody::log_and_get_request_body( &req, utils::Encode::::url_encode, @@ -272,14 +272,14 @@ impl &self, req: &types::ConnectorCustomerRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let connector_request = stripe::CustomerRequest::try_from(req)?; + ) -> CustomResult { + connector_req = stripe::CustomerRequest::try_from(req)?; let stripe_req = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stripe_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -402,14 +402,14 @@ impl &self, req: &types::TokenizationRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let connector_request = stripe::TokenRequest::try_from(req)?; + ) -> CustomResult { + connector_req = stripe::TokenRequest::try_from(req)?; let stripe_req = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stripe_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -534,14 +534,14 @@ impl &self, req: &types::PaymentsCaptureRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let connector_request = stripe::CaptureRequest::try_from(req)?; + ) -> CustomResult { + connector_req = stripe::CaptureRequest::try_from(req)?; let stripe_req = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stripe_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -823,13 +823,13 @@ impl &self, req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { match &req.request.payment_method_data { api_models::payments::PaymentMethodData::BankTransfer(bank_transfer_data) => { stripe::get_bank_transfer_request_data(req, bank_transfer_data.deref()) } _ => { - let req = stripe::PaymentIntentRequest::try_from(req)?; + let connector_req = stripe::PaymentIntentRequest::try_from(req)?; let request = types::RequestBody::log_and_get_request_body( &req, utils::Encode::::url_encode, @@ -966,14 +966,14 @@ impl &self, req: &types::PaymentsCancelRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let connector_request = stripe::CancelRequest::try_from(req)?; + ) -> CustomResult { + connector_req = stripe::CancelRequest::try_from(req)?; let stripe_req = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stripe_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -1103,14 +1103,14 @@ impl types::PaymentsResponseData, >, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req = stripe::SetupIntentRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = stripe::SetupIntentRequest::try_from(req)?; let stripe_req = types::RequestBody::log_and_get_request_body( &req, utils::Encode::::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stripe_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -1240,14 +1240,14 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let connector_request = stripe::RefundRequest::try_from(req)?; + ) -> CustomResult { + connector_req = stripe::RefundRequest::try_from(req)?; let stripe_req = types::RequestBody::log_and_get_request_body( &connector_request, utils::Encode::::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(stripe_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -1497,7 +1497,7 @@ impl req: &types::UploadFileRouterData, ) -> CustomResult, errors::ConnectorError> { let stripe_req = transformers::construct_file_upload_request(req.clone())?; - Ok(Some(stripe_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -1722,7 +1722,7 @@ impl &self, req: &types::SubmitEvidenceRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let stripe_req = stripe::Evidence::try_from(req)?; let stripe_req_string = types::RequestBody::log_and_get_request_body( &stripe_req, diff --git a/crates/router/src/connector/trustpay.rs b/crates/router/src/connector/trustpay.rs index 65ab5a7ba58d..cdd199ac03bd 100644 --- a/crates/router/src/connector/trustpay.rs +++ b/crates/router/src/connector/trustpay.rs @@ -237,14 +237,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = trustpay::TrustpayAuthUpdateRequest::try_from(req)?; let trustpay_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(trustpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -440,7 +440,7 @@ impl &self, req: &types::PaymentsPreProcessingRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let currency = req.request.get_currency()?; let amount = req .request @@ -454,14 +454,14 @@ impl amount, req, ))?; - let create_intent_req = + let connector_req = trustpay::TrustpayCreateIntentRequest::try_from(&connector_router_data)?; let trustpay_req = types::RequestBody::log_and_get_request_body( &create_intent_req, utils::Encode::::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(trustpay_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -558,7 +558,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let amount = req .request .surcharge_details @@ -586,7 +586,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = trustpay::TrustpayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -699,7 +699,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = tsys::TsysPaymentsRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = tsys::TsysPaymentsRequest::try_from(req)?; let tsys_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(tsys_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -230,14 +230,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = tsys::TsysSyncRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = tsys::TsysSyncRequest::try_from(req)?; let tsys_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(tsys_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -312,14 +312,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = tsys::TsysPaymentsCaptureRequest::try_from(req)?; let tsys_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(tsys_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -395,14 +395,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = tsys::TsysPaymentsCancelRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = tsys::TsysPaymentsCancelRequest::try_from(req)?; let tsys_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(tsys_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -473,14 +473,14 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let req_obj = tsys::TsysRefundRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = tsys::TsysRefundRequest::try_from(req)?; let tsys_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(tsys_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -554,14 +554,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = tsys::TsysSyncRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = tsys::TsysSyncRequest::try_from(req)?; let tsys_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(tsys_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/volt.rs b/crates/router/src/connector/volt.rs index 43b6b3a3406d..ff794a9a7972 100644 --- a/crates/router/src/connector/volt.rs +++ b/crates/router/src/connector/volt.rs @@ -175,15 +175,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let req_obj = volt::VoltAuthUpdateRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = volt::VoltAuthUpdateRequest::try_from(req)?; let volt_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::url_encode, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(volt_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -266,20 +266,20 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = volt::VoltRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = volt::VoltPaymentsRequest::try_from(&connector_router_data)?; + let connector_req = volt::VoltPaymentsRequest::try_from(&connector_router_data)?; let volt_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(volt_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -425,7 +425,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) } @@ -507,20 +507,20 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = volt::VoltRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = volt::VoltRefundRequest::try_from(&connector_router_data)?; + let connector_req = volt::VoltRefundRequest::try_from(&connector_router_data)?; let volt_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(volt_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/wise.rs b/crates/router/src/connector/wise.rs index 865dcd5fff35..c01788392b68 100644 --- a/crates/router/src/connector/wise.rs +++ b/crates/router/src/connector/wise.rs @@ -327,14 +327,14 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = wise::WisePayoutQuoteRequest::try_from(req)?; let wise_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(wise_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -405,14 +405,14 @@ impl &self, req: &types::PayoutsRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = wise::WiseRecipientCreateRequest::try_from(req)?; let wise_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(wise_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -523,14 +523,14 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = wise::WisePayoutCreateRequest::try_from(req)?; let wise_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(wise_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -622,14 +622,14 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = wise::WisePayoutFulfillRequest::try_from(req)?; let wise_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(wise_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/worldline.rs b/crates/router/src/connector/worldline.rs index 3d928624df8f..6a2059388faa 100644 --- a/crates/router/src/connector/worldline.rs +++ b/crates/router/src/connector/worldline.rs @@ -379,7 +379,7 @@ impl ConnectorIntegration, _connectors: &Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = worldline::ApproveRequest::try_from(req)?; let worldline_req = types::RequestBody::log_and_get_request_body( @@ -387,7 +387,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(worldline_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -493,7 +493,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = worldline::WorldlineRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -506,7 +506,7 @@ impl ConnectorIntegration::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(worldline_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -595,14 +595,14 @@ impl ConnectorIntegration, _connectors: &Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_req = worldline::WorldlineRefundRequest::try_from(req)?; let refund_req = types::RequestBody::log_and_get_request_body( &connector_req, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(refund_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/worldpay.rs b/crates/router/src/connector/worldpay.rs index ef01aa9a6ada..88285bbd02fa 100644 --- a/crates/router/src/connector/worldpay.rs +++ b/crates/router/src/connector/worldpay.rs @@ -435,20 +435,20 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = worldpay::WorldpayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let connector_request = WorldpayPaymentsRequest::try_from(&connector_router_data)?; + let connector_req = WorldpayPaymentsRequest::try_from(&connector_router_data)?; let worldpay_payment_request = types::RequestBody::log_and_get_request_body( &connector_request, ext_traits::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(worldpay_payment_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -521,14 +521,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let connector_request = WorldpayRefundRequest::try_from(req)?; + ) -> CustomResult { + let connector_req = WorldpayRefundRequest::try_from(req)?; let fiserv_refund_request = types::RequestBody::log_and_get_request_body( &connector_request, ext_traits::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(fiserv_refund_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn get_url( diff --git a/crates/router/src/connector/zen.rs b/crates/router/src/connector/zen.rs index 102d54bab427..654e3997b391 100644 --- a/crates/router/src/connector/zen.rs +++ b/crates/router/src/connector/zen.rs @@ -217,20 +217,20 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = zen::ZenRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.amount, req, ))?; - let req_obj = zen::ZenPaymentsRequest::try_from(&connector_router_data)?; + let connector_req = zen::ZenPaymentsRequest::try_from(&connector_router_data)?; let zen_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(zen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -415,20 +415,20 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = zen::ZenRouterData::try_from(( &self.get_currency_unit(), req.request.currency, req.request.refund_amount, req, ))?; - let req_obj = zen::ZenRefundRequest::try_from(&connector_router_data)?; + let connector_req = zen::ZenRefundRequest::try_from(&connector_router_data)?; let zen_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, ) .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(zen_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 0a8b84ffd11c..b02808f70988 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -16,6 +16,7 @@ pub use common_utils::request::{ContentType, Method, Request, RequestBuilder}; use common_utils::{ consts::X_HS_LATENCY, errors::{ErrorSwitch, ReportSwitchExt}, + request::RequestContent, }; use error_stack::{report, IntoReport, Report, ResultExt}; use masking::{ExposeOptionInterface, PeekInterface}; @@ -133,7 +134,7 @@ pub trait ConnectorIntegration: ConnectorIntegrationAny, _connectors: &Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { Ok(None) } From c97c76ac1801b023c17861f3f8c80a06e11b8f18 Mon Sep 17 00:00:00 2001 From: Sampras lopes Date: Wed, 22 Nov 2023 13:48:22 +0530 Subject: [PATCH 02/19] feat(events): remove request logging --- crates/common_utils/src/request.rs | 15 +++- crates/router/src/connector/aci.rs | 25 ++---- crates/router/src/connector/adyen.rs | 78 +++---------------- crates/router/src/connector/airwallex.rs | 48 +++--------- .../router/src/connector/authorizedotnet.rs | 51 ++---------- crates/router/src/connector/bambora.rs | 38 ++------- crates/router/src/connector/bankofamerica.rs | 31 ++------ crates/router/src/connector/bitpay.rs | 8 +- crates/router/src/connector/bluesnap.rs | 48 ++---------- crates/router/src/connector/boku.rs | 31 ++------ crates/router/src/connector/braintree.rs | 75 +++--------------- crates/router/src/connector/cashtocode.rs | 8 +- crates/router/src/connector/checkout.rs | 47 +++-------- crates/router/src/connector/coinbase.rs | 8 +- crates/router/src/connector/cryptopay.rs | 8 +- crates/router/src/connector/cybersource.rs | 43 +++------- crates/router/src/connector/dlocal.rs | 34 ++------ crates/router/src/connector/dummyconnector.rs | 17 +--- crates/router/src/connector/fiserv.rs | 43 ++-------- crates/router/src/connector/forte.rs | 29 ++----- crates/router/src/connector/globalpay.rs | 44 +++-------- crates/router/src/connector/globepay.rs | 15 +--- crates/router/src/connector/gocardless.rs | 36 ++------- crates/router/src/connector/helcim.rs | 38 ++------- crates/router/src/connector/iatapay.rs | 24 ++---- crates/router/src/connector/klarna.rs | 15 +--- crates/router/src/connector/mollie.rs | 22 +----- crates/router/src/connector/multisafepay.rs | 17 +--- crates/router/src/connector/nexinets.rs | 29 ++----- crates/router/src/connector/nmi.rs | 50 ++---------- crates/router/src/connector/noon.rs | 31 ++------ crates/router/src/connector/nuvei.rs | 57 +++----------- crates/router/src/connector/opayo.rs | 19 ++--- crates/router/src/connector/opennode.rs | 8 +- crates/router/src/connector/payeezy.rs | 29 ++----- crates/router/src/connector/payme.rs | 57 +++----------- crates/router/src/connector/paypal.rs | 37 ++------- crates/router/src/connector/payu.rs | 29 ++----- crates/router/src/connector/powertranz.rs | 36 ++------- crates/router/src/connector/prophetpay.rs | 41 ++-------- crates/router/src/connector/rapyd.rs | 22 +----- crates/router/src/connector/shift4.rs | 31 ++------ crates/router/src/connector/square.rs | 22 +----- crates/router/src/connector/stax.rs | 36 ++------- crates/router/src/connector/stripe.rs | 67 +++------------- crates/router/src/connector/trustpay.rs | 39 ++-------- crates/router/src/connector/tsys.rs | 43 ++-------- crates/router/src/connector/volt.rs | 24 ++---- crates/router/src/connector/wise.rs | 29 ++----- crates/router/src/connector/worldline.rs | 22 +----- crates/router/src/connector/worldpay.rs | 19 ++--- crates/router/src/connector/zen.rs | 17 +--- 52 files changed, 310 insertions(+), 1380 deletions(-) diff --git a/crates/common_utils/src/request.rs b/crates/common_utils/src/request.rs index 36997f773ff0..d07be26bb9ee 100644 --- a/crates/common_utils/src/request.rs +++ b/crates/common_utils/src/request.rs @@ -2,6 +2,7 @@ use masking::{Maskable, Secret}; #[cfg(feature = "logs")] use router_env::logger; use serde::{Deserialize, Serialize}; +use serde_json::json; use crate::errors; @@ -191,7 +192,17 @@ impl RequestBody { logger::info!(connector_request_body=?body); Ok(Self(Secret::new(encoder(body)?))) } - pub fn get_inner_value(request_body: Self) -> Secret { - request_body.0 + // pub fn get_inner_value(request_body: Self) -> Secret { + // request_body.0 + // } + // pub fn get_inner_value(request_body: Self) -> Secret { + // request_body.0 + // } + + pub fn get_inner_value(request_body: RequestContent) -> Secret { + match request_body { + RequestContent::Json(i) | RequestContent::FormUrlEncoded(i) | RequestContent::Xml(i) => serde_json::to_string(&i.raw_serialize().unwrap_or(json!(""))).unwrap_or_default().into(), + RequestContent::FormData(_) => String::new().into(), + } } } diff --git a/crates/router/src/connector/aci.rs b/crates/router/src/connector/aci.rs index f41f442875f5..bdc51fe3da0b 100644 --- a/crates/router/src/connector/aci.rs +++ b/crates/router/src/connector/aci.rs @@ -1,8 +1,8 @@ mod result_codes; pub mod transformers; use std::fmt::Debug; - use common_utils::request::RequestContent; + use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as aci; @@ -202,7 +202,7 @@ impl .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) - .body(types::PaymentsSyncType::get_request_body( + .set_body(types::PaymentsSyncType::get_request_body( self, req, connectors, )?) .build(), @@ -293,11 +293,6 @@ impl req, ))?; let connector_req = aci::AciPaymentsRequest::try_from(&connector_router_data)?; - let aci_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -321,7 +316,7 @@ impl .headers(types::PaymentsAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsAuthorizeType::get_request_body( + .set_body(types::PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -395,11 +390,6 @@ impl _connectors: &settings::Connectors, ) -> CustomResult { let connector_req = aci::AciCancelRequest::try_from(req)?; - let aci_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -413,7 +403,7 @@ impl .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( + .set_body(types::PaymentsVoidType::get_request_body( self, req, connectors, )?) .build(), @@ -497,11 +487,6 @@ impl services::ConnectorIntegration::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -518,7 +503,7 @@ impl services::ConnectorIntegration>::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -204,7 +200,7 @@ impl .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( + .set_body(types::SetupMandateType::get_request_body( self, req, connectors, )?) .build(), @@ -318,11 +314,6 @@ impl req, ))?; let connector_req = adyen::AdyenCaptureRequest::try_from(&connector_router_data)?; - let adyen_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -338,7 +329,7 @@ impl .headers(types::PaymentsCaptureType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCaptureType::get_request_body( + .set_body(types::PaymentsCaptureType::get_request_body( self, req, connectors, )?) .build(), @@ -460,11 +451,6 @@ impl } }; - let adyen_request = types::RequestBody::log_and_get_request_body( - &redirection_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(adyen_request)) } @@ -497,7 +483,7 @@ impl .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) - .body(types::PaymentsSyncType::get_request_body( + .set_body(types::PaymentsSyncType::get_request_body( self, req, connectors, )?) .build(), @@ -650,11 +636,6 @@ impl req, ))?; let connector_req = adyen::AdyenPaymentRequest::try_from(&connector_router_data)?; - let request_body = types::RequestBody::log_and_get_request_body( - &connector_req, - common_utils::ext_traits::Encode::>::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(request_body)) } @@ -674,7 +655,7 @@ impl .headers(types::PaymentsAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsAuthorizeType::get_request_body( + .set_body(types::PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -768,11 +749,6 @@ impl ) -> CustomResult { let connector_req = adyen::AdyenBalanceRequest::try_from(req)?; - let adyen_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::>::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -789,7 +765,7 @@ impl .headers(types::PaymentsBalanceType::get_headers( self, req, connectors, )?) - .body(types::PaymentsBalanceType::get_request_body( + .set_body(types::PaymentsBalanceType::get_request_body( self, req, connectors, )?) .build(), @@ -864,11 +840,6 @@ impl ) -> CustomResult { let connector_req = adyen::AdyenCancelRequest::try_from(req)?; - let adyen_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -882,7 +853,7 @@ impl .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( + .set_body(types::PaymentsVoidType::get_request_body( self, req, connectors, )?) .build(), @@ -976,11 +947,6 @@ impl services::ConnectorIntegration CustomResult { let connector_req = adyen::AdyenPayoutCancelRequest::try_from(req)?; - let adyen_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -994,7 +960,7 @@ impl services::ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -1088,7 +1049,7 @@ impl services::ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -1187,7 +1143,7 @@ impl .headers(types::PayoutEligibilityType::get_headers( self, req, connectors, )?) - .body(types::PayoutEligibilityType::get_request_body( + .set_body(types::PayoutEligibilityType::get_request_body( self, req, connectors, )?) .build(); @@ -1277,11 +1233,6 @@ impl services::ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -1297,7 +1248,7 @@ impl services::ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -1400,7 +1346,7 @@ impl services::ConnectorIntegration CustomResult { let req_obj = airwallex::AirwallexIntentRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(req_obj))) } @@ -277,7 +272,7 @@ impl .url(&types::PaymentsInitType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsInitType::get_headers(self, req, connectors)?) - .body(types::PaymentsInitType::get_request_body( + .set_body(types::PaymentsInitType::get_request_body( self, req, connectors, )?) .build(), @@ -387,11 +382,6 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -410,7 +400,7 @@ impl ConnectorIntegration CustomResult { let req_obj = airwallex::AirwallexCompleteRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(req_obj))) } fn build_request( @@ -580,7 +565,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -648,11 +633,6 @@ impl ConnectorIntegration CustomResult { let connector_req = airwallex::AirwallexPaymentsCaptureRequest::try_from(req)?; - let airwallex_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -670,7 +650,7 @@ impl ConnectorIntegration CustomResult { let connector_req = airwallex::AirwallexPaymentsCancelRequest::try_from(req)?; - let airwallex_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn handle_response( @@ -786,7 +761,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -864,7 +834,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -177,7 +172,7 @@ impl ConnectorIntegration CustomResult { let connector_req = authorizedotnet::AuthorizedotnetCreateSyncRequest::try_from(req)?; - let sync_request = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(sync_request)) } @@ -265,7 +255,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -369,7 +354,7 @@ impl ConnectorIntegration CustomResult { let connector_req = authorizedotnet::CancelOrCaptureTransactionRequest::try_from(req)?; - let authorizedotnet_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -455,7 +435,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -555,7 +530,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(sync_request)) } @@ -650,7 +620,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -753,7 +718,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), diff --git a/crates/router/src/connector/bambora.rs b/crates/router/src/connector/bambora.rs index bd1b3c48a2b0..79931ed61782 100644 --- a/crates/router/src/connector/bambora.rs +++ b/crates/router/src/connector/bambora.rs @@ -1,6 +1,7 @@ pub mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; @@ -177,11 +178,6 @@ impl ConnectorIntegration CustomResult { let connector_req = bambora::BamboraPaymentsRequest::try_from(req)?; - let bambora_req = types::RequestBody::log_and_get_request_body( - &request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -196,7 +192,7 @@ impl ConnectorIntegration CustomResult { let connector_req = bambora::BamboraPaymentsCaptureRequest::try_from(req)?; - let bambora_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -370,7 +361,7 @@ impl ConnectorIntegration CustomResult { let connector_req = bambora::BamboraPaymentsRequest::try_from(req)?; - let bambora_req = types::RequestBody::log_and_get_request_body( - &request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -467,7 +453,7 @@ impl ConnectorIntegration CustomResult { let connector_req = bambora::BamboraRefundRequest::try_from(req)?; - let bambora_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -563,7 +544,7 @@ impl ConnectorIntegration CustomResult { let connector_req = bambora::BamboraThreedsContinueRequest::try_from(&req.request)?; - let bambora_req = types::RequestBody::log_and_get_request_body( - &request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -776,7 +752,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(); diff --git a/crates/router/src/connector/bankofamerica.rs b/crates/router/src/connector/bankofamerica.rs index 399acc1e4d28..c1c5ebe64383 100644 --- a/crates/router/src/connector/bankofamerica.rs +++ b/crates/router/src/connector/bankofamerica.rs @@ -1,6 +1,7 @@ pub mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use base64::Engine; use diesel_models::enums; @@ -311,11 +312,6 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -334,7 +330,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -500,7 +491,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -599,7 +585,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -690,7 +671,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -206,7 +202,7 @@ impl ConnectorIntegration CustomResult { let connector_req = bluesnap::BluesnapVoidRequest::try_from(req)?; - let bluesnap_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -284,7 +280,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -469,7 +460,7 @@ impl ConnectorIntegration CustomResult { let connector_req = bluesnap::BluesnapCreateWalletToken::try_from(req)?; - let bluesnap_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -559,7 +545,7 @@ impl ConnectorIntegration { let connector_req = bluesnap::BluesnapPaymentsTokenRequest::try_from(&connector_router_data)?; - let bluesnap_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } _ => { let connector_req = bluesnap::BluesnapPaymentsRequest::try_from(&connector_router_data)?; - let bluesnap_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } } @@ -678,7 +654,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -801,7 +772,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -877,11 +848,6 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -897,7 +863,7 @@ impl ConnectorIntegration CustomResult { let connector_req = boku::BokuPaymentsRequest::try_from(req)?; - let boku_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_xml, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -233,7 +229,7 @@ impl ConnectorIntegration CustomResult { let connector_req = boku::BokuPsyncRequest::try_from(req)?; - let boku_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_xml, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -322,7 +313,7 @@ impl ConnectorIntegration CustomResult { let connector_req = boku::BokuRefundRequest::try_from(req)?; - let boku_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_xml, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -495,7 +481,7 @@ impl ConnectorIntegration CustomResult { let connector_req = boku::BokuRsyncRequest::try_from(req)?; - let boku_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_xml, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -577,7 +558,7 @@ impl ConnectorIntegration CustomResult { let connector_request = braintree::BraintreeSessionRequest::try_from(req)?; - let braintree_session_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -332,11 +327,6 @@ impl let connector_request = braintree_graphql_transformers::BraintreeTokenRequest::try_from(req)?; - let braintree_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -353,7 +343,7 @@ impl .url(&types::TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::TokenizationType::get_headers(self, req, connectors)?) - .body(types::TokenizationType::get_request_body( + .set_body(types::TokenizationType::get_request_body( self, req, connectors, )?) .build(), @@ -462,11 +452,6 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } false => Err(errors::ConnectorError::NotImplemented( @@ -491,7 +476,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } false => Ok(None), @@ -630,7 +610,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(braintree_payment_request)) } false => { let connector_request = braintree::BraintreePaymentsRequest::try_from(req)?; - let braintree_payment_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(braintree_payment_request)) } } @@ -937,7 +907,7 @@ impl ConnectorIntegration { let connector_request = braintree_graphql_transformers::BraintreeCancelRequest::try_from(req)?; - let braintree_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } false => Ok(None), @@ -1093,20 +1058,10 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(braintree_refund_request)) } false => { let connector_request = braintree::BraintreeRefundRequest::try_from(req)?; - let braintree_refund_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(braintree_refund_request)) } } @@ -1124,7 +1079,7 @@ impl ConnectorIntegration { let connector_request = braintree_graphql_transformers::BraintreeRSyncRequest::try_from(req)?; - let braintree_refund_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(braintree_refund_request)) } false => Ok(None), @@ -1245,7 +1195,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(braintree_payment_request)) } false => Err(errors::ConnectorError::NotImplemented( @@ -1653,7 +1598,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), diff --git a/crates/router/src/connector/cashtocode.rs b/crates/router/src/connector/cashtocode.rs index 2cbd4f762e7e..ac8e6c8568ce 100644 --- a/crates/router/src/connector/cashtocode.rs +++ b/crates/router/src/connector/cashtocode.rs @@ -1,6 +1,7 @@ pub mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use base64::Engine; use diesel_models::enums; @@ -206,11 +207,6 @@ impl ConnectorIntegration CustomResult { let connector_req = cashtocode::CashtocodePaymentsRequest::try_from(req)?; - let cashtocode_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -229,7 +225,7 @@ impl ConnectorIntegration CustomResult { let connector_req = checkout::TokenRequest::try_from(req)?; - let checkout_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -230,7 +226,7 @@ impl .url(&types::TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::TokenizationType::get_headers(self, req, connectors)?) - .body(types::TokenizationType::get_request_body( + .set_body(types::TokenizationType::get_request_body( self, req, connectors, )?) .build(), @@ -325,11 +321,6 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -346,7 +337,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -541,7 +527,7 @@ impl ConnectorIntegration CustomResult { let connector_req = checkout::PaymentVoidRequest::try_from(req)?; - let checkout_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -621,7 +602,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(body)) } @@ -719,7 +695,7 @@ impl ConnectorIntegration CustomResult { let checkout_req = checkout::Evidence::try_from(req)?; - let checkout_req_string = types::RequestBody::log_and_get_request_body( - &checkout_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(checkout_req_string)) } @@ -1085,7 +1056,7 @@ impl .headers(types::SubmitEvidenceType::get_headers( self, req, connectors, )?) - .body(types::SubmitEvidenceType::get_request_body( + .set_body(types::SubmitEvidenceType::get_request_body( self, req, connectors, )?) .build(); diff --git a/crates/router/src/connector/coinbase.rs b/crates/router/src/connector/coinbase.rs index 1c13b61ef30b..f539ea54e0fb 100644 --- a/crates/router/src/connector/coinbase.rs +++ b/crates/router/src/connector/coinbase.rs @@ -1,6 +1,7 @@ pub mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use common_utils::{crypto, ext_traits::ByteSliceExt}; use diesel_models::enums; @@ -187,11 +188,6 @@ impl ConnectorIntegration CustomResult { let connector_req = coinbase::CoinbasePaymentsRequest::try_from(req)?; - let coinbase_payment_request = types::RequestBody::log_and_get_request_body( - &connector_request, - Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -209,7 +205,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -249,7 +245,7 @@ impl ConnectorIntegration CustomResult { let connector_req = cybersource::CybersourceZeroMandateRequest::try_from(req)?; - let cybersource_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -294,7 +290,7 @@ impl .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( + .set_body(types::SetupMandateType::get_request_body( self, req, connectors, )?) .build(), @@ -382,11 +378,6 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -402,7 +393,7 @@ impl ConnectorIntegration CustomResult { - Ok(Some( - types::RequestBody::log_and_get_request_body("{}".to_string(), Ok) - .change_context(errors::ConnectorError::RequestEncodingFailed)?, - )) + Ok(RequestContent::Json(Box::new(json!({})))) } fn build_request( &self, @@ -567,11 +555,6 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -589,7 +572,7 @@ impl ConnectorIntegration CustomResult { - Ok(Some( - types::RequestBody::log_and_get_request_body("{}".to_string(), Ok) - .change_context(errors::ConnectorError::RequestEncodingFailed)?, - )) + Ok(RequestContent::Json(serde_json::json!({}))) } fn build_request( @@ -675,7 +655,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(cybersource_refund_request)) } fn build_request( @@ -774,7 +749,7 @@ impl ConnectorIntegration CustomResult)>, errors::ConnectorError> { - let dlocal_req = match self.get_request_body(req, connectors)? { - Some(val) => val, - None => types::RequestBody::log_and_get_request_body("".to_string(), Ok) - .change_context(errors::ConnectorError::RequestEncodingFailed)?, - }; - - let date = date_time::date_as_yyyymmddthhmmssmmmz() - .into_report() - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - + let dlocal_req = self.get_request_body(req, connectors)?; + let auth = dlocal::DlocalAuthType::try_from(&req.connector_auth_type)?; let sign_req: String = format!( "{}{}{}", @@ -220,11 +213,6 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -243,7 +231,7 @@ impl ConnectorIntegration CustomResult { let connector_req = dlocal::DlocalPaymentsCaptureRequest::try_from(req)?; - let dlocal_payments_capture_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -396,7 +379,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -553,7 +531,7 @@ impl ConnectorIntegration _connectors: &settings::Connectors, ) -> CustomResult { let connector_req = transformers::DummyConnectorPaymentsRequest::::try_from(req)?; - let dummmy_payments_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::>::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -214,7 +210,7 @@ impl .headers(types::PaymentsAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsAuthorizeType::get_request_body( + .set_body(types::PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -437,11 +433,6 @@ impl ConnectorIntegration CustomResult { let connector_req = transformers::DummyConnectorRefundRequest::try_from(req)?; - let dummmy_refund_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -457,7 +448,7 @@ impl ConnectorIntegration ConnectorIntegration CustomResult { let connector_req = fiserv::FiservCancelRequest::try_from(req)?; - let fiserv_payments_cancel_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(fiserv_payments_cancel_request)) } @@ -268,7 +264,7 @@ impl ConnectorIntegration CustomResult { let connector_req = fiserv::FiservSyncRequest::try_from(req)?; - let fiserv_payments_sync_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -357,7 +348,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -441,7 +427,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -557,7 +538,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -649,7 +625,7 @@ impl ConnectorIntegration CustomResult { let connector_req = fiserv::FiservSyncRequest::try_from(req)?; - let fiserv_sync_request = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -732,7 +703,7 @@ impl ConnectorIntegration CustomResult { let connector_req = forte::FortePaymentsRequest::try_from(req)?; - let forte_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -227,7 +223,7 @@ impl ConnectorIntegration CustomResult { let connector_req = forte::ForteCaptureRequest::try_from(req)?; - let forte_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -384,7 +375,7 @@ impl ConnectorIntegration CustomResult { let connector_req = forte::ForteCancelRequest::try_from(req)?; - let forte_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -468,7 +454,7 @@ impl ConnectorIntegration CustomResult { let connector_req = forte::ForteRefundRequest::try_from(req)?; - let forte_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -552,7 +533,7 @@ impl ConnectorIntegration CustomResult { - let globalpay_req = types::RequestBody::log_and_get_request_body("{}".to_string(), Ok) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::Json(serde_json::json!({}))) } fn build_request( @@ -187,7 +186,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -264,7 +263,7 @@ impl ConnectorIntegration CustomResult { let connector_req = GlobalpayRefreshTokenRequest::try_from(req)?; - let globalpay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -387,7 +381,7 @@ impl ConnectorIntegration CustomResult { let connector_req = requests::GlobalpayCancelRequest::try_from(req)?; - let globalpay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -551,11 +540,6 @@ impl ConnectorIntegration CustomResult { let connector_req = requests::GlobalpayCaptureRequest::try_from(req)?; - let globalpay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -572,7 +556,7 @@ impl ConnectorIntegration CustomResult { let connector_req = GlobalpayPaymentsRequest::try_from(req)?; - let globalpay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -666,7 +645,7 @@ impl ConnectorIntegration CustomResult { let connector_req = requests::GlobalpayRefundRequest::try_from(req)?; - let globalpay_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -756,7 +730,7 @@ impl ConnectorIntegration CustomResult { let connector_req = globepay::GlobepayPaymentsRequest::try_from(req)?; - let globepay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -214,7 +210,7 @@ impl ConnectorIntegration CustomResult { let connector_req = globepay::GlobepayRefundRequest::try_from(req)?; - let globepay_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -391,7 +382,7 @@ impl ConnectorIntegration CustomResult { let connector_req = gocardless::GocardlessCustomerRequest::try_from(req)?; - let gocardless_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -179,7 +175,7 @@ impl .headers(types::ConnectorCustomerType::get_headers( self, req, connectors, )?) - .body(types::ConnectorCustomerType::get_request_body( + .set_body(types::ConnectorCustomerType::get_request_body( self, req, connectors, )?) .build(), @@ -254,11 +250,6 @@ impl _connectors: &settings::Connectors, ) -> CustomResult { let connector_req = gocardless::GocardlessBankAccountRequest::try_from(req)?; - let gocardless_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -273,7 +264,7 @@ impl .url(&types::TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::TokenizationType::get_headers(self, req, connectors)?) - .body(types::TokenizationType::get_request_body( + .set_body(types::TokenizationType::get_request_body( self, req, connectors, )?) .build(), @@ -375,11 +366,6 @@ impl _connectors: &settings::Connectors, ) -> CustomResult { let connector_req = gocardless::GocardlessMandateRequest::try_from(req)?; - let gocardless_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -396,7 +382,7 @@ impl .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( + .set_body(types::SetupMandateType::get_request_body( self, req, connectors, )?) .build(), @@ -466,11 +452,6 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -489,7 +470,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -654,7 +630,7 @@ impl ConnectorIntegration CustomResult { 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(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -215,7 +211,7 @@ impl .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( + .set_body(types::SetupMandateType::get_request_body( self, req, connectors, )?) .build(), @@ -282,11 +278,6 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -305,7 +296,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -477,7 +463,7 @@ impl ConnectorIntegration CustomResult { let connector_req = 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(RequestContent::Json(Box::new(connector_req))) } @@ -556,7 +537,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -641,7 +617,7 @@ impl ConnectorIntegration CustomResult { let connector_req = iatapay::IatapayAuthUpdateRequest::try_from(req)?; - let iatapay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -200,7 +196,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -311,7 +302,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -497,7 +483,7 @@ impl ConnectorIntegration CustomResult { let connector_req = klarna::KlarnaSessionRequest::try_from(req)?; // encode only for for urlencoded things. - let klarna_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -179,7 +175,7 @@ impl .headers(types::PaymentsSessionType::get_headers( self, req, connectors, )?) - .body(types::PaymentsSessionType::get_request_body( + .set_body(types::PaymentsSessionType::get_request_body( self, req, connectors, )?) .build(), @@ -423,11 +419,6 @@ impl req, ))?; let connector_req = klarna::KlarnaPaymentsRequest::try_from(&connector_router_data)?; - let klarna_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -446,7 +437,7 @@ impl .headers(types::PaymentsAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsAuthorizeType::get_request_body( + .set_body(types::PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(), diff --git a/crates/router/src/connector/mollie.rs b/crates/router/src/connector/mollie.rs index 49a5fcd170f0..c9f97caed17f 100644 --- a/crates/router/src/connector/mollie.rs +++ b/crates/router/src/connector/mollie.rs @@ -1,6 +1,7 @@ pub mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; @@ -153,11 +154,6 @@ impl _connectors: &settings::Connectors, ) -> CustomResult { let connector_req = mollie::MollieCardTokenRequest::try_from(req)?; - let mollie_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -171,7 +167,7 @@ impl .url(&types::TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::TokenizationType::get_headers(self, req, connectors)?) - .body(types::TokenizationType::get_request_body( + .set_body(types::TokenizationType::get_request_body( self, req, connectors, )?) .build(), @@ -245,11 +241,6 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -268,7 +259,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -462,7 +448,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -295,7 +291,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -397,7 +388,7 @@ impl ConnectorIntegration CustomResult { let connector_req = nexinets::NexinetsPaymentsRequest::try_from(req)?; - let nexinets_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -226,7 +222,7 @@ impl ConnectorIntegration CustomResult { let connector_req = nexinets::NexinetsCaptureOrVoidRequest::try_from(req)?; - let nexinets_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -389,7 +380,7 @@ impl ConnectorIntegration CustomResult { let connector_req = nexinets::NexinetsCaptureOrVoidRequest::try_from(req)?; - let nexinets_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -473,7 +459,7 @@ impl ConnectorIntegration CustomResult { let connector_req = nexinets::NexinetsRefundRequest::try_from(req)?; - let nexinets_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -561,7 +542,7 @@ impl ConnectorIntegration CustomResult { let connector_req = nmi::NmiPaymentsRequest::try_from(req)?; - let nmi_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -164,7 +160,7 @@ impl .method(services::Method::Post) .url(&types::SetupMandateType::get_url(self, req, connectors)?) .headers(types::SetupMandateType::get_headers(self, req, connectors)?) - .body(types::SetupMandateType::get_request_body( + .set_body(types::SetupMandateType::get_request_body( self, req, connectors, )?) .build(), @@ -225,11 +221,6 @@ impl ConnectorIntegration::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -247,7 +238,7 @@ impl ConnectorIntegration CustomResult { let connector_req = nmi::NmiSyncRequest::try_from(req)?; - let nmi_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -320,7 +306,7 @@ impl ConnectorIntegration::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -398,7 +379,7 @@ impl ConnectorIntegration CustomResult { let connector_req = nmi::NmiCancelRequest::try_from(req)?; - let nmi_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -471,7 +447,7 @@ impl ConnectorIntegration::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -550,7 +521,7 @@ impl ConnectorIntegration CustomResult { let connector_req = nmi::NmiSyncRequest::try_from(req)?; - let nmi_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -621,7 +587,7 @@ impl ConnectorIntegration CustomResult { let connector_req = noon::NoonPaymentsRequest::try_from(req)?; - let noon_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -236,7 +232,7 @@ impl ConnectorIntegration CustomResult { let connector_req = noon::NoonPaymentsActionRequest::try_from(req)?; - let noon_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -383,7 +374,7 @@ impl ConnectorIntegration CustomResult { let connector_req = noon::NoonPaymentsCancelRequest::try_from(req)?; - let noon_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -461,7 +447,7 @@ impl ConnectorIntegration CustomResult { let connector_req = noon::NoonPaymentsActionRequest::try_from(req)?; - let noon_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -539,7 +520,7 @@ impl ConnectorIntegration CustomResult { let meta: nuvei::NuveiMeta = utils::to_connector_meta(req.request.connector_meta.clone())?; let connector_req = nuvei::NuveiPaymentsRequest::try_from((req, meta.session_token))?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - common_utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -176,7 +172,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -239,11 +235,6 @@ impl ConnectorIntegration CustomResult { let connector_req = nuvei::NuveiPaymentFlowRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - common_utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -257,7 +248,7 @@ impl ConnectorIntegration CustomResult { let connector_req = nuvei::NuveiPaymentSyncRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - common_utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -344,7 +330,7 @@ impl ConnectorIntegration CustomResult { let connector_req = nuvei::NuveiPaymentFlowRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - common_utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -429,7 +410,7 @@ impl ConnectorIntegration CustomResult { let connector_req = nuvei::NuveiPaymentsRequest::try_from((req, req.get_session_token()?))?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - common_utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -592,7 +568,7 @@ impl ConnectorIntegration CustomResult { let connector_req = nuvei::NuveiSessionRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - common_utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -683,7 +654,7 @@ impl .headers(types::PaymentsPreAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsPreAuthorizeType::get_request_body( + .set_body(types::PaymentsPreAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -744,11 +715,6 @@ impl ConnectorIntegration CustomResult { let connector_req = nuvei::NuveiPaymentsRequest::try_from((req, req.get_session_token()?))?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - common_utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -764,7 +730,7 @@ impl ConnectorIntegration CustomResult { let connector_req = nuvei::NuveiPaymentFlowRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - common_utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -846,7 +807,7 @@ impl ConnectorIntegration CustomResult { let connector_req = opayo::OpayoPaymentsRequest::try_from(req)?; - let opayo_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -199,7 +195,7 @@ impl ConnectorIntegration CustomResult { let connector_req = opayo::OpayoRefundRequest::try_from(req)?; - let opayo_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -420,7 +411,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -204,7 +200,7 @@ impl ConnectorIntegration CustomResult { let connector_req = payeezy::PayeezyCaptureOrVoidRequest::try_from(req)?; - let payeezy_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(payeezy_req)) } @@ -221,7 +217,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -329,7 +320,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -429,7 +415,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -524,7 +505,7 @@ impl ConnectorIntegration CustomResult { let connector_req = payme::CaptureBuyerRequest::try_from(req)?; - let payme_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -174,7 +170,7 @@ impl .url(&types::TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::TokenizationType::get_headers(self, req, connectors)?) - .body(types::TokenizationType::get_request_body( + .set_body(types::TokenizationType::get_request_body( self, req, connectors, )?) .build(), @@ -263,11 +259,6 @@ impl let connector_router_data = payme::PaymeRouterData::try_from((&self.get_currency_unit(), currency, amount, req))?; let connector_req = payme::GenerateSaleRequest::try_from(&connector_router_data)?; - let payme_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -286,7 +277,7 @@ impl .url(&types::PaymentsPreProcessingType::get_url( self, req, connectors, )?) - .body(types::PaymentsPreProcessingType::get_request_body( + .set_body(types::PaymentsPreProcessingType::get_request_body( self, req, connectors, )?) .build(), @@ -385,11 +376,6 @@ impl _connectors: &settings::Connectors, ) -> CustomResult { let connector_req = payme::Pay3dsRequest::try_from(req)?; - let payme_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -407,7 +393,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -494,11 +480,6 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -517,7 +498,7 @@ impl ConnectorIntegration CustomResult { let connector_req = payme::PaymeQuerySaleRequest::try_from(req)?; - let payme_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -604,7 +580,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -700,7 +671,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -808,7 +774,7 @@ impl ConnectorIntegration CustomResult { let connector_req = payme::PaymeQueryTransactionRequest::try_from(req)?; - let payme_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -892,7 +853,7 @@ impl ConnectorIntegration CustomResult { let connector_req = paypal::PaypalAuthUpdateRequest::try_from(req)?; - let paypal_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -339,7 +334,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -453,7 +443,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -758,7 +743,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -921,7 +901,7 @@ impl ConnectorIntegration CustomResult { let connector_req = paypal::PaypalSourceVerificationRequest::try_from(&req.request)?; - let paypal_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } diff --git a/crates/router/src/connector/payu.rs b/crates/router/src/connector/payu.rs index 867042abe9cb..3c59bf9921c7 100644 --- a/crates/router/src/connector/payu.rs +++ b/crates/router/src/connector/payu.rs @@ -1,6 +1,7 @@ pub mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; @@ -247,11 +248,6 @@ impl ConnectorIntegration CustomResult { let connector_req = payu::PayuAuthUpdateRequest::try_from(req)?; - let payu_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -267,7 +263,7 @@ impl ConnectorIntegration CustomResult { let connector_req = payu::PayuPaymentsCaptureRequest::try_from(req)?; - let payu_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -445,7 +436,7 @@ impl ConnectorIntegration CustomResult { let connector_req = payu::PayuPaymentsRequest::try_from(req)?; - let payu_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -548,7 +534,7 @@ impl ConnectorIntegration CustomResult { let connector_req = payu::PayuRefundRequest::try_from(req)?; - let payu_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -638,7 +619,7 @@ impl ConnectorIntegration CustomResult { let connector_req = powertranz::PowertranzPaymentsRequest::try_from(req)?; - let powertranz_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -220,7 +216,7 @@ impl ConnectorIntegration CustomResult { let connector_req = powertranz::PowertranzBaseRequest::try_from(&req.request)?; - let powertranz_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -399,7 +385,7 @@ impl ConnectorIntegration CustomResult { let connector_req = powertranz::PowertranzBaseRequest::try_from(&req.request)?; - let powertranz_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -490,7 +471,7 @@ impl ConnectorIntegration CustomResult { let connector_req = powertranz::PowertranzBaseRequest::try_from(req)?; - let powertranz_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -554,7 +530,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -207,7 +203,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -309,7 +300,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -376,11 +367,6 @@ impl ConnectorIntegration CustomResult { let connector_req = prophetpay::ProphetpaySyncRequest::try_from(req)?; - let prophetpay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -395,7 +381,7 @@ impl ConnectorIntegration CustomResult { let connector_req =prophetpay::ProphetpayVoidRequest::try_from(req)?; - let prophetpay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } */ @@ -548,11 +529,6 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -568,7 +544,7 @@ impl ConnectorIntegration CustomResult { let connector_req = prophetpay::ProphetpayRefundSyncRequest::try_from(req)?; - let prophetpay_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -652,7 +623,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -234,7 +230,7 @@ impl self, req, connectors, )?) .headers(headers) - .body(types::PaymentsAuthorizeType::get_request_body( + .set_body(types::PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(); @@ -505,11 +501,6 @@ impl req, ))?; let connector_req = rapyd::CaptureRequest::try_from(&connector_router_data)?; - let rapyd_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -545,7 +536,7 @@ impl self, req, connectors, )?) .headers(headers) - .body(types::PaymentsCaptureType::get_request_body( + .set_body(types::PaymentsCaptureType::get_request_body( self, req, connectors, )?) .build(); @@ -646,11 +637,6 @@ impl services::ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -680,7 +666,7 @@ impl services::ConnectorIntegration CustomResult { let connector_req = shift4::Shift4PaymentsRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -250,7 +246,7 @@ impl ConnectorIntegration CustomResult { let connector_req = shift4::Shift4PaymentsRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -500,7 +491,7 @@ impl .content_type(request::ContentType::FormUrlEncoded) .attach_default_headers() .headers(types::PaymentsInitType::get_headers(self, req, connectors)?) - .body(types::PaymentsInitType::get_request_body( + .set_body(types::PaymentsInitType::get_request_body( self, req, connectors, )?) .build(), @@ -565,11 +556,6 @@ impl _connectors: &settings::Connectors, ) -> CustomResult { let connector_req = shift4::Shift4PaymentsRequest::try_from(req)?; - let req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -587,7 +573,7 @@ impl .headers(types::PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -646,11 +632,6 @@ impl ConnectorIntegration CustomResult { let connector_req = shift4::Shift4RefundRequest::try_from(req)?; - let shift4_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(shift4_req)) } @@ -666,7 +647,7 @@ impl ConnectorIntegration CustomResult { let connector_request = square::SquareTokenRequest::try_from(req)?; - let square_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -269,7 +265,7 @@ impl .url(&types::TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::TokenizationType::get_headers(self, req, connectors)?) - .body(types::TokenizationType::get_request_body( + .set_body(types::TokenizationType::get_request_body( self, req, connectors, )?) .build(), @@ -420,11 +416,6 @@ impl ConnectorIntegration CustomResult { let connector_req = square::SquarePaymentsRequest::try_from(req)?; - let square_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -443,7 +434,7 @@ impl ConnectorIntegration CustomResult { let connector_req = square::SquareRefundRequest::try_from(req)?; - let square_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -732,7 +718,7 @@ impl ConnectorIntegration CustomResult { let connector_request = stax::StaxCustomerRequest::try_from(req)?; - let stax_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -188,7 +184,7 @@ impl .headers(types::ConnectorCustomerType::get_headers( self, req, connectors, )?) - .body(types::ConnectorCustomerType::get_request_body( + .set_body(types::ConnectorCustomerType::get_request_body( self, req, connectors, )?) .build(), @@ -257,11 +253,6 @@ impl ) -> CustomResult { let connector_request = stax::StaxTokenRequest::try_from(req)?; - let stax_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -276,7 +267,7 @@ impl .url(&types::TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::TokenizationType::get_headers(self, req, connectors)?) - .body(types::TokenizationType::get_request_body( + .set_body(types::TokenizationType::get_request_body( self, req, connectors, )?) .build(), @@ -367,11 +358,6 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -390,7 +376,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -552,7 +533,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -714,7 +690,7 @@ impl ConnectorIntegration CustomResult { let connector_req = stripe::StripeCreditTransferSourceRequest::try_from(req)?; - let pre_processing_request = types::RequestBody::log_and_get_request_body( - &req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(pre_processing_request)) } @@ -171,7 +166,7 @@ impl .headers(types::PaymentsPreProcessingType::get_headers( self, req, connectors, )?) - .body(types::PaymentsPreProcessingType::get_request_body( + .set_body(types::PaymentsPreProcessingType::get_request_body( self, req, connectors, )?) .build(), @@ -274,11 +269,6 @@ impl _connectors: &settings::Connectors, ) -> CustomResult { connector_req = stripe::CustomerRequest::try_from(req)?; - let stripe_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -297,7 +287,7 @@ impl .headers(types::ConnectorCustomerType::get_headers( self, req, connectors, )?) - .body(types::ConnectorCustomerType::get_request_body( + .set_body(types::ConnectorCustomerType::get_request_body( self, req, connectors, )?) .build(), @@ -404,11 +394,6 @@ impl _connectors: &settings::Connectors, ) -> CustomResult { connector_req = stripe::TokenRequest::try_from(req)?; - let stripe_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -423,7 +408,7 @@ impl .url(&types::TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::TokenizationType::get_headers(self, req, connectors)?) - .body(types::TokenizationType::get_request_body( + .set_body(types::TokenizationType::get_request_body( self, req, connectors, )?) .build(), @@ -536,11 +521,6 @@ impl _connectors: &settings::Connectors, ) -> CustomResult { connector_req = stripe::CaptureRequest::try_from(req)?; - let stripe_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -557,7 +537,7 @@ impl .headers(types::PaymentsCaptureType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCaptureType::get_request_body( + .set_body(types::PaymentsCaptureType::get_request_body( self, req, connectors, )?) .build(), @@ -681,7 +661,7 @@ impl .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) - .body(types::PaymentsSyncType::get_request_body( + .set_body(types::PaymentsSyncType::get_request_body( self, req, connectors, )?) .build(), @@ -830,11 +810,6 @@ impl } _ => { let connector_req = stripe::PaymentIntentRequest::try_from(req)?; - let request = types::RequestBody::log_and_get_request_body( - &req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(request)) } } @@ -855,7 +830,7 @@ impl .headers(types::PaymentsAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsAuthorizeType::get_request_body( + .set_body(types::PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -968,11 +943,6 @@ impl _connectors: &settings::Connectors, ) -> CustomResult { connector_req = stripe::CancelRequest::try_from(req)?; - let stripe_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -986,7 +956,7 @@ impl .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( + .set_body(types::PaymentsVoidType::get_request_body( self, req, connectors, )?) .build(); @@ -1105,11 +1075,6 @@ impl _connectors: &settings::Connectors, ) -> CustomResult { let connector_req = stripe::SetupIntentRequest::try_from(req)?; - let stripe_req = types::RequestBody::log_and_get_request_body( - &req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -1128,7 +1093,7 @@ impl .url(&Verify::get_url(self, req, connectors)?) .attach_default_headers() .headers(Verify::get_headers(self, req, connectors)?) - .body(Verify::get_request_body(self, req, connectors)?) + .set_body(Verify::get_request_body(self, req, connectors)?) .build(), )) } @@ -1242,11 +1207,6 @@ impl services::ConnectorIntegration CustomResult { connector_req = stripe::RefundRequest::try_from(req)?; - let stripe_req = types::RequestBody::log_and_get_request_body( - &connector_request, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -1262,7 +1222,7 @@ impl services::ConnectorIntegration CustomResult { let stripe_req = stripe::Evidence::try_from(req)?; - let stripe_req_string = types::RequestBody::log_and_get_request_body( - &stripe_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(Some(stripe_req_string)) } @@ -1744,7 +1699,7 @@ impl .headers(types::SubmitEvidenceType::get_headers( self, req, connectors, )?) - .body(types::SubmitEvidenceType::get_request_body( + .set_body(types::SubmitEvidenceType::get_request_body( self, req, connectors, )?) .build(); diff --git a/crates/router/src/connector/trustpay.rs b/crates/router/src/connector/trustpay.rs index cdd199ac03bd..b21e84f85a7c 100644 --- a/crates/router/src/connector/trustpay.rs +++ b/crates/router/src/connector/trustpay.rs @@ -1,6 +1,7 @@ pub mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use base64::Engine; use common_utils::{crypto, errors::ReportSwitchExt, ext_traits::ByteSliceExt}; @@ -239,11 +240,6 @@ impl ConnectorIntegration CustomResult { let connector_req = trustpay::TrustpayAuthUpdateRequest::try_from(req)?; - let trustpay_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -258,7 +254,7 @@ impl ConnectorIntegration::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -479,7 +470,7 @@ impl .url(&types::PaymentsPreProcessingType::get_url( self, req, connectors, )?) - .body(types::PaymentsPreProcessingType::get_request_body( + .set_body(types::PaymentsPreProcessingType::get_request_body( self, req, connectors, )?) .build(), @@ -574,17 +565,7 @@ impl ConnectorIntegration { - types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)? } - _ => types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?, }; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -604,7 +585,7 @@ impl ConnectorIntegration { - types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)? } _ => - types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?, }; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -714,7 +685,7 @@ impl ConnectorIntegration CustomResult { let connector_req = tsys::TsysPaymentsRequest::try_from(req)?; - let tsys_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -169,7 +165,7 @@ impl ConnectorIntegration CustomResult { let connector_req = tsys::TsysSyncRequest::try_from(req)?; - let tsys_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -251,7 +242,7 @@ impl ConnectorIntegration CustomResult { let connector_req = tsys::TsysPaymentsCaptureRequest::try_from(req)?; - let tsys_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -335,7 +321,7 @@ impl ConnectorIntegration CustomResult { let connector_req = tsys::TsysPaymentsCancelRequest::try_from(req)?; - let tsys_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -414,7 +395,7 @@ impl ConnectorIntegration CustomResult { let connector_req = tsys::TsysRefundRequest::try_from(req)?; - let tsys_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -495,7 +471,7 @@ impl ConnectorIntegration CustomResult { let connector_req = tsys::TsysSyncRequest::try_from(req)?; - let tsys_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -575,7 +546,7 @@ impl ConnectorIntegration CustomResult { let connector_req = volt::VoltAuthUpdateRequest::try_from(req)?; - let volt_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -197,7 +193,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -297,7 +288,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -535,7 +521,7 @@ impl ConnectorIntegration CustomResult { let connector_req = wise::WisePayoutQuoteRequest::try_from(req)?; - let wise_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -347,7 +343,7 @@ impl services::ConnectorIntegration CustomResult { let connector_req = wise::WiseRecipientCreateRequest::try_from(req)?; - let wise_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -427,7 +418,7 @@ impl .headers(types::PayoutRecipientType::get_headers( self, req, connectors, )?) - .body(types::PayoutRecipientType::get_request_body( + .set_body(types::PayoutRecipientType::get_request_body( self, req, connectors, )?) .build(); @@ -525,11 +516,6 @@ impl services::ConnectorIntegration CustomResult { let connector_req = wise::WisePayoutCreateRequest::try_from(req)?; - let wise_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -543,7 +529,7 @@ impl services::ConnectorIntegration CustomResult { let connector_req = wise::WisePayoutFulfillRequest::try_from(req)?; - let wise_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -644,7 +625,7 @@ impl services::ConnectorIntegration CustomResult { let connector_req = worldline::ApproveRequest::try_from(req)?; - let worldline_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -407,7 +403,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -528,7 +519,7 @@ impl ConnectorIntegration CustomResult { let connector_req = worldline::WorldlineRefundRequest::try_from(req)?; - let refund_req = types::RequestBody::log_and_get_request_body( - &connector_req, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -617,7 +603,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -466,7 +462,7 @@ impl ConnectorIntegration CustomResult { let connector_req = WorldpayRefundRequest::try_from(req)?; - let fiserv_refund_request = types::RequestBody::log_and_get_request_body( - &connector_request, - ext_traits::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -556,7 +547,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -248,7 +244,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -443,7 +434,7 @@ impl ConnectorIntegration Date: Wed, 22 Nov 2023 18:57:52 +0530 Subject: [PATCH 03/19] refactor(events): add request content type --- crates/common_utils/src/request.rs | 12 +--- crates/router/Cargo.toml | 1 + crates/router/src/connector/adyen.rs | 62 ++++++++++--------- .../router/src/connector/authorizedotnet.rs | 4 +- crates/router/src/connector/bankofamerica.rs | 5 +- crates/router/src/connector/braintree.rs | 43 ++++++------- crates/router/src/connector/checkout.rs | 19 +++--- crates/router/src/connector/cryptopay.rs | 20 ++---- crates/router/src/connector/cybersource.rs | 11 ++-- crates/router/src/connector/dlocal.rs | 3 + crates/router/src/connector/fiserv.rs | 5 +- crates/router/src/connector/globalpay.rs | 5 +- crates/router/src/connector/nuvei.rs | 2 +- crates/router/src/connector/payeezy.rs | 7 +-- crates/router/src/connector/paypal.rs | 2 +- crates/router/src/connector/powertranz.rs | 2 +- crates/router/src/connector/shift4.rs | 5 +- crates/router/src/connector/square.rs | 2 +- crates/router/src/connector/stax.rs | 4 +- crates/router/src/connector/stripe.rs | 43 ++++++------- crates/router/src/connector/trustpay.rs | 8 +-- .../src/core/payment_methods/transformers.rs | 34 +++------- .../src/core/payments/flows/session_flow.rs | 12 +--- crates/router/src/core/verification.rs | 10 +-- crates/router/src/core/webhooks.rs | 25 ++++---- crates/router/src/services/api.rs | 61 ++++++++---------- 26 files changed, 174 insertions(+), 233 deletions(-) diff --git a/crates/common_utils/src/request.rs b/crates/common_utils/src/request.rs index d07be26bb9ee..26fcf204aea3 100644 --- a/crates/common_utils/src/request.rs +++ b/crates/common_utils/src/request.rs @@ -56,10 +56,10 @@ impl std::fmt::Debug for RequestContent { } pub enum RequestContent { - Json(Box), - FormUrlEncoded(Box), + Json(Box), + FormUrlEncoded(Box), FormData(reqwest::multipart::Form), - Xml(Box), + Xml(Box), } impl Request { @@ -192,12 +192,6 @@ impl RequestBody { logger::info!(connector_request_body=?body); Ok(Self(Secret::new(encoder(body)?))) } - // pub fn get_inner_value(request_body: Self) -> Secret { - // request_body.0 - // } - // pub fn get_inner_value(request_body: Self) -> Secret { - // request_body.0 - // } pub fn get_inner_value(request_body: RequestContent) -> Secret { match request_body { diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 25feb373b734..90a94ae114b0 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -116,6 +116,7 @@ router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra scheduler = { version = "0.1.0", path = "../scheduler", default-features = false } storage_impl = { version = "0.1.0", path = "../storage_impl", default-features = false } erased-serde = "0.3.31" +quick-xml = { version = "0.31.0", features = ["serialize"] } [build-dependencies] router_env = { version = "0.1.0", path = "../router_env", default-features = false } diff --git a/crates/router/src/connector/adyen.rs b/crates/router/src/connector/adyen.rs index 6b067b6325a0..4f5f8553553b 100644 --- a/crates/router/src/connector/adyen.rs +++ b/crates/router/src/connector/adyen.rs @@ -397,25 +397,20 @@ impl req: &types::RouterData, _connectors: &settings::Connectors, ) -> CustomResult { - // Adyen doesn't support PSync flow. We use PSync flow to fetch payment details, - // specifically the redirect URL that takes the user to their Payment page. In non-redirection flows, - // we rely on webhooks to obtain the payment status since there is no encoded data available. - // encoded_data only includes the redirect URL and is only relevant in redirection flows. - let encoded_value = req + + + let encoded_data = req .request .encoded_data .clone() - .get_required_value("encoded_data"); - - match encoded_value { - Ok(encoded_data) => { + .get_required_value("encoded_data")?; let adyen_redirection_type = serde_urlencoded::from_str::< transformers::AdyenRedirectRequestTypes, >(encoded_data.as_str()) .into_report() .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - let redirection_request = match adyen_redirection_type { + let connector_req = match adyen_redirection_type { adyen::AdyenRedirectRequestTypes::AdyenRedirection(req) => { adyen::AdyenRedirectRequest { details: adyen::AdyenRedirectRequestTypes::AdyenRedirection( @@ -452,11 +447,8 @@ impl }; - Ok(Some(adyen_request)) - } - Err(_) => Ok(None), + Ok(RequestContent::Json(Box::new(connector_req))) } - } fn get_url( &self, @@ -475,21 +467,31 @@ impl req: &types::RouterData, connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { - let request_body = self.get_request_body(req, connectors)?; - match request_body { - Some(_) => Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) - .set_body(types::PaymentsSyncType::get_request_body( - self, req, connectors, - )?) - .build(), - )), - None => Ok(None), - } + // Adyen doesn't support PSync flow. We use PSync flow to fetch payment details, + // specifically the redirect URL that takes the user to their Payment page. In non-redirection flows, + // we rely on webhooks to obtain the payment status since there is no encoded data available. + // encoded_data only includes the redirect URL and is only relevant in redirection flows. + if req + .request + .encoded_data + .clone() + .get_required_value("encoded_data") + .is_ok() { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .set_body(types::PaymentsSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } else { + Ok(None) + } + } fn handle_response( @@ -636,7 +638,7 @@ impl req, ))?; let connector_req = adyen::AdyenPaymentRequest::try_from(&connector_router_data)?; - Ok(Some(request_body)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/authorizedotnet.rs b/crates/router/src/connector/authorizedotnet.rs index 5485df764365..349bbe01e4b8 100644 --- a/crates/router/src/connector/authorizedotnet.rs +++ b/crates/router/src/connector/authorizedotnet.rs @@ -242,7 +242,7 @@ impl ConnectorIntegration CustomResult { let connector_req = authorizedotnet::AuthorizedotnetCreateSyncRequest::try_from(req)?; - Ok(Some(sync_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -607,7 +607,7 @@ impl ConnectorIntegration CustomResult { - let connector_request = braintree::BraintreeSessionRequest::try_from(req)?; + let connector_req = braintree::BraintreeSessionRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -324,7 +324,7 @@ impl req: &types::TokenizationRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_request = + let connector_req = braintree_graphql_transformers::BraintreeTokenRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -447,7 +447,7 @@ impl ConnectorIntegration { - let connector_request = + let connector_req = braintree_graphql_transformers::BraintreeCaptureRequest::try_from( &connector_router_data, )?; @@ -588,12 +588,12 @@ impl ConnectorIntegration { - let connector_request = + let connector_req = braintree_graphql_transformers::BraintreePSyncRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) } - false => Ok(None), + false => Err(errors::ConnectorError::RequestEncodingFailed).into_report(), } } @@ -766,15 +766,15 @@ impl ConnectorIntegration { - let connector_request = + let connector_req = braintree_graphql_transformers::BraintreePaymentsRequest::try_from( &connector_router_data, )?; - Ok(Some(braintree_payment_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } false => { - let connector_request = braintree::BraintreePaymentsRequest::try_from(req)?; - Ok(Some(braintree_payment_request)) + let connector_req = braintree::BraintreePaymentsRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } } } @@ -922,11 +922,11 @@ impl ConnectorIntegration { - let connector_request = + let connector_req= braintree_graphql_transformers::BraintreeCancelRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) } - false => Ok(None), + false => Err(errors::ConnectorError::RequestEncodingFailed).into_report(), } } @@ -1054,15 +1054,15 @@ impl ConnectorIntegration { - let connector_request = + let connector_req = braintree_graphql_transformers::BraintreeRefundRequest::try_from( connector_router_data, )?; - Ok(Some(braintree_refund_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } false => { - let connector_request = braintree::BraintreeRefundRequest::try_from(req)?; - Ok(Some(braintree_refund_request)) + let connector_req = braintree::BraintreeRefundRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } } } @@ -1174,11 +1174,11 @@ impl ConnectorIntegration { - let connector_request = + let connector_req = braintree_graphql_transformers::BraintreeRSyncRequest::try_from(req)?; - Ok(Some(braintree_refund_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } - false => Ok(None), + false => Err(errors::ConnectorError::RequestEncodingFailed).into_report(), } } @@ -1570,17 +1570,18 @@ impl let connector_api_version = &req.connector_api_version; match self.is_braintree_graphql_version(connector_api_version) { true => { - let connector_request = + let connector_req= braintree_graphql_transformers::BraintreePaymentsRequest::try_from( &connector_router_data, )?; - Ok(Some(braintree_payment_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } false => Err(errors::ConnectorError::NotImplemented( "get_request_body method".to_string(), ))?, } } + fn build_request( &self, req: &types::PaymentsCompleteAuthorizeRouterData, diff --git a/crates/router/src/connector/checkout.rs b/crates/router/src/connector/checkout.rs index 00188ae9cd49..0620e4a30843 100644 --- a/crates/router/src/connector/checkout.rs +++ b/crates/router/src/connector/checkout.rs @@ -680,7 +680,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let checkout_req = transformers::construct_file_upload_request(req.clone())?; - Ok(RequestContent::Json(Box::new(connector_req))) + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_req = transformers::construct_file_upload_request(req.clone())?; + Ok(RequestContent::FormData(connector_req)) } + fn build_request( &self, @@ -963,8 +965,7 @@ impl ConnectorIntegration CustomResult { - let checkout_req = checkout::Evidence::try_from(req)?; - Ok(Some(checkout_req_string)) + let connector_req = checkout::Evidence::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/cryptopay.rs b/crates/router/src/connector/cryptopay.rs index 498941b42a3c..89232ea2f8b7 100644 --- a/crates/router/src/connector/cryptopay.rs +++ b/crates/router/src/connector/cryptopay.rs @@ -70,20 +70,12 @@ where connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { let api_method; - let payload = match self.get_request_body(req, connectors)? { - Some(val) => { - let body = types::RequestBody::get_inner_value(val).peek().to_owned(); - api_method = "POST".to_string(); - let md5_payload = crypto::Md5 - .generate_digest(body.as_bytes()) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - encode(md5_payload) - } - None => { - api_method = "GET".to_string(); - String::default() - } - }; + let body = types::RequestBody::get_inner_value(self.get_request_body(req, connectors)?).peek().to_owned(); + api_method = "POST".to_string(); + let md5_payload = crypto::Md5 + .generate_digest(body.as_bytes()) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + let payload = encode(md5_payload); let now = date_time::date_as_yyyymmddthhmmssmmmz() .into_report() diff --git a/crates/router/src/connector/cybersource.rs b/crates/router/src/connector/cybersource.rs index 734ff3c839f0..85dea3cef667 100644 --- a/crates/router/src/connector/cybersource.rs +++ b/crates/router/src/connector/cybersource.rs @@ -184,10 +184,7 @@ where .skip(base_url.len() - 1) .collect(); let sha256 = self.generate_digest( - cybersource_req - .map_or("{}".to_string(), |s| { - types::RequestBody::get_inner_value(s).expose() - }) + types::RequestBody::get_inner_value(cybersource_req).expose() .as_bytes(), ); let http_method = self.get_http_method(); @@ -471,7 +468,7 @@ impl ConnectorIntegration CustomResult { - Ok(RequestContent::Json(Box::new(json!({})))) + Ok(RequestContent::Json(Box::new(serde_json::json!({})))) } fn build_request( &self, @@ -641,7 +638,7 @@ impl ConnectorIntegration CustomResult { - Ok(RequestContent::Json(serde_json::json!({}))) + Ok(RequestContent::Json(Box::new(serde_json::json!({})))) } fn build_request( @@ -734,7 +731,7 @@ impl ConnectorIntegration CustomResult { let connector_req = fiserv::FiservCancelRequest::try_from(req)?; - Ok(Some(fiserv_payments_cancel_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/globalpay.rs b/crates/router/src/connector/globalpay.rs index e8b1eb5cb55b..276bd2c87875 100644 --- a/crates/router/src/connector/globalpay.rs +++ b/crates/router/src/connector/globalpay.rs @@ -3,9 +3,8 @@ mod response; pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; -use ::common_utils::{errors::ReportSwitchExt, ext_traits::ByteSliceExt}; +use ::common_utils::{errors::ReportSwitchExt, ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; @@ -168,7 +167,7 @@ impl _req: &types::PaymentsCompleteAuthorizeRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - Ok(RequestContent::Json(serde_json::json!({}))) + Ok(RequestContent::Json(Box::new(serde_json::json!({})))) } fn build_request( diff --git a/crates/router/src/connector/nuvei.rs b/crates/router/src/connector/nuvei.rs index 7efb28ddf481..ab380966f552 100644 --- a/crates/router/src/connector/nuvei.rs +++ b/crates/router/src/connector/nuvei.rs @@ -1,10 +1,10 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use ::common_utils::{ crypto, + request::RequestContent, errors::ReportSwitchExt, ext_traits::{BytesExt, ValueExt}, }; diff --git a/crates/router/src/connector/payeezy.rs b/crates/router/src/connector/payeezy.rs index f11ef4ac4b41..19f788cb8848 100644 --- a/crates/router/src/connector/payeezy.rs +++ b/crates/router/src/connector/payeezy.rs @@ -43,10 +43,7 @@ where connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { let auth = payeezy::PayeezyAuthType::try_from(&req.connector_auth_type)?; - let option_request_payload = self.get_request_body(req, connectors)?; - let request_payload = option_request_payload.map_or("{}".to_string(), |payload| { - types::RequestBody::get_inner_value(payload).expose() - }); + let request_payload = types::RequestBody::get_inner_value(self.get_request_body(req, connectors)?).expose(); let timestamp = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .ok() @@ -204,7 +201,7 @@ impl ConnectorIntegration CustomResult { let connector_req = payeezy::PayeezyCaptureOrVoidRequest::try_from(req)?; - Ok(Some(payeezy_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/paypal.rs b/crates/router/src/connector/paypal.rs index af40ed423b41..48ec9b8e76ac 100644 --- a/crates/router/src/connector/paypal.rs +++ b/crates/router/src/connector/paypal.rs @@ -2,7 +2,7 @@ pub mod transformers; use std::fmt::{Debug, Write}; use base64::Engine; -use common_utils::ext_traits::ByteSliceExt; +use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, PeekInterface, Secret}; diff --git a/crates/router/src/connector/powertranz.rs b/crates/router/src/connector/powertranz.rs index ad15ed7f41c3..99099d50c99b 100644 --- a/crates/router/src/connector/powertranz.rs +++ b/crates/router/src/connector/powertranz.rs @@ -285,7 +285,7 @@ impl .parse_value("PowerTranz RedirectResponsePayload") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; let spi_token = format!(r#""{}""#, redirect_payload.spi_token); - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::Json(Box::new(spi_token))) } fn build_request( diff --git a/crates/router/src/connector/shift4.rs b/crates/router/src/connector/shift4.rs index 7142da8e8e51..39dccbdc19ef 100644 --- a/crates/router/src/connector/shift4.rs +++ b/crates/router/src/connector/shift4.rs @@ -476,7 +476,7 @@ impl _connectors: &settings::Connectors, ) -> CustomResult { let connector_req = shift4::Shift4PaymentsRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -488,7 +488,6 @@ impl services::RequestBuilder::new() .method(services::Method::Post) .url(&types::PaymentsInitType::get_url(self, req, connectors)?) - .content_type(request::ContentType::FormUrlEncoded) .attach_default_headers() .headers(types::PaymentsInitType::get_headers(self, req, connectors)?) .set_body(types::PaymentsInitType::get_request_body( @@ -632,7 +631,7 @@ impl ConnectorIntegration CustomResult { let connector_req = shift4::Shift4RefundRequest::try_from(req)?; - Ok(Some(shift4_req)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/square.rs b/crates/router/src/connector/square.rs index 455b4276c969..1f2dbe0832ca 100644 --- a/crates/router/src/connector/square.rs +++ b/crates/router/src/connector/square.rs @@ -249,7 +249,7 @@ impl req: &types::TokenizationRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_request = square::SquareTokenRequest::try_from(req)?; + let connector_req = square::SquareTokenRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) } diff --git a/crates/router/src/connector/stax.rs b/crates/router/src/connector/stax.rs index 28f118dcf75a..85d5b5ae683d 100644 --- a/crates/router/src/connector/stax.rs +++ b/crates/router/src/connector/stax.rs @@ -164,7 +164,7 @@ impl req: &types::ConnectorCustomerRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_request = stax::StaxCustomerRequest::try_from(req)?; + let connector_req = stax::StaxCustomerRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -251,7 +251,7 @@ impl req: &types::TokenizationRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_request = stax::StaxTokenRequest::try_from(req)?; + let connector_req = stax::StaxTokenRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) } diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index f9fe8ead5083..8855ae7d0cd1 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -2,6 +2,7 @@ pub mod transformers; use std::{collections::HashMap, fmt::Debug, ops::Deref}; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; @@ -148,7 +149,7 @@ impl ) -> CustomResult { let connector_req = stripe::StripeCreditTransferSourceRequest::try_from(req)?; - Ok(Some(pre_processing_request)) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -268,8 +269,8 @@ impl req: &types::ConnectorCustomerRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - connector_req = stripe::CustomerRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + let connector_req = stripe::CustomerRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -393,8 +394,8 @@ impl req: &types::TokenizationRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - connector_req = stripe::TokenRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + let connector_req = stripe::TokenRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -520,8 +521,8 @@ impl req: &types::PaymentsCaptureRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - connector_req = stripe::CaptureRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + let connector_req = stripe::CaptureRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -810,7 +811,7 @@ impl } _ => { let connector_req = stripe::PaymentIntentRequest::try_from(req)?; - Ok(Some(request)) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } } } @@ -942,8 +943,8 @@ impl req: &types::PaymentsCancelRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - connector_req = stripe::CancelRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + let connector_req = stripe::CancelRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -1075,7 +1076,7 @@ impl _connectors: &settings::Connectors, ) -> CustomResult { let connector_req = stripe::SetupIntentRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -1206,8 +1207,8 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, ) -> CustomResult { - connector_req = stripe::RefundRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + let connector_req = stripe::RefundRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -1452,12 +1453,13 @@ impl )) } - fn get_request_form_data( + fn get_request_body( &self, req: &types::UploadFileRouterData, - ) -> CustomResult, errors::ConnectorError> { - let stripe_req = transformers::construct_file_upload_request(req.clone())?; - Ok(RequestContent::Json(Box::new(connector_req))) + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_req = transformers::construct_file_upload_request(req.clone())?; + Ok(RequestContent::FormUrlEncoded(connector_req)) } fn build_request( @@ -1471,8 +1473,7 @@ impl .url(&types::UploadFileType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::UploadFileType::get_headers(self, req, connectors)?) - .form_data(types::UploadFileType::get_request_form_data(self, req)?) - .content_type(services::request::ContentType::FormData) + .set_body(types::UploadFileType::get_request_body(self, req, connectors)) .build(), )) } @@ -1683,8 +1684,8 @@ impl req: &types::SubmitEvidenceRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let stripe_req = stripe::Evidence::try_from(req)?; - Ok(Some(stripe_req_string)) + let connector_req = stripe::Evidence::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/trustpay.rs b/crates/router/src/connector/trustpay.rs index b21e84f85a7c..1dc1c2bf1f61 100644 --- a/crates/router/src/connector/trustpay.rs +++ b/crates/router/src/connector/trustpay.rs @@ -665,12 +665,12 @@ impl ConnectorIntegration { + Ok(RequestContent::Json(Box::new(connector_req))) } - _ => - }; - Ok(RequestContent::Json(Box::new(connector_req))) + _ => Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) + } } fn build_request( diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs index 3b4d057e6025..0c88efe19789 100644 --- a/crates/router/src/core/payment_methods/transformers.rs +++ b/crates/router/src/core/payment_methods/transformers.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use api_models::enums as api_enums; -use common_utils::{ext_traits::StringExt, pii::Email}; +use common_utils::{ext_traits::StringExt, pii::Email, request::RequestContent}; use error_stack::ResultExt; use josekit::jwe; use serde::{Deserialize, Serialize}; @@ -323,7 +323,7 @@ pub async fn mk_add_locker_request_hs<'a>( url.push_str("/cards/add"); let mut request = services::Request::new(services::Method::Post, &url); request.add_header(headers::CONTENT_TYPE, "application/json".into()); - request.set_body(body.to_string()); + request.set_body(RequestContent::Json(Box::new(body.to_string()))); Ok(request) } @@ -421,16 +421,10 @@ pub fn mk_add_card_request( name_on_card: Some("John Doe".to_string().into()), // [#256] nickname: Some("router".to_string()), // }; - let body = utils::Encode::>::url_encode(&add_card_req) - .change_context(errors::VaultError::RequestEncodingFailed)?; let mut url = locker.host.to_owned(); url.push_str("/card/addCard"); let mut request = services::Request::new(services::Method::Post, &url); - request.add_header( - headers::CONTENT_TYPE, - "application/x-www-form-urlencoded".into(), - ); - request.set_body(body); + request.set_body(RequestContent::FormUrlEncoded(Box::new(add_card_req))); Ok(request) } @@ -475,7 +469,7 @@ pub async fn mk_get_card_request_hs( url.push_str("/cards/retrieve"); let mut request = services::Request::new(services::Method::Post, &url); request.add_header(headers::CONTENT_TYPE, "application/json".into()); - request.set_body(body.to_string()); + request.set_body(RequestContent::Json(Box::new(body.to_string()))); Ok(request) } @@ -489,16 +483,10 @@ pub fn mk_get_card_request<'a>( card_id, }; - let body = utils::Encode::>::url_encode(&get_card_req) - .change_context(errors::VaultError::RequestEncodingFailed)?; let mut url = locker.host.to_owned(); url.push_str("/card/getCard"); let mut request = services::Request::new(services::Method::Post, &url); - request.add_header( - headers::CONTENT_TYPE, - "application/x-www-form-urlencoded".into(), - ); - request.set_body(body); + request.set_body(RequestContent::FormUrlEncoded(Box::new(get_card_req))); Ok(request) } @@ -555,7 +543,7 @@ pub async fn mk_delete_card_request_hs( url.push_str("/cards/delete"); let mut request = services::Request::new(services::Method::Post, &url); request.add_header(headers::CONTENT_TYPE, "application/json".into()); - request.set_body(body.to_string()); + request.set_body(RequestContent::Json(Box::new(body.to_string()))); Ok(request) } @@ -568,18 +556,12 @@ pub fn mk_delete_card_request<'a>( merchant_id, card_id, }; - let body = utils::Encode::>::url_encode(&delete_card_req) - .change_context(errors::VaultError::RequestEncodingFailed)?; let mut url = locker.host.to_owned(); url.push_str("/card/deleteCard"); let mut request = services::Request::new(services::Method::Post, &url); request.add_default_headers(); - request.add_header( - headers::CONTENT_TYPE, - "application/x-www-form-urlencoded".into(), - ); - request.set_body(body); + request.set_body(RequestContent::FormUrlEncoded(Box::new(delete_card_req))); Ok(request) } @@ -627,7 +609,7 @@ pub fn mk_crud_locker_request( let mut request = services::Request::new(services::Method::Post, &url); request.add_default_headers(); request.add_header(headers::CONTENT_TYPE, "application/json".into()); - request.set_body(body.to_string()); + request.set_body(RequestContent::Json(Box::new(body.to_string()))); Ok(request) } diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 595a6f5e958e..ca4d693b3c59 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -1,6 +1,6 @@ use api_models::payments as payment_types; use async_trait::async_trait; -use common_utils::ext_traits::ByteSliceExt; +use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; use error_stack::{IntoReport, Report, ResultExt}; #[cfg(feature = "kms")] use external_services::kms; @@ -15,7 +15,7 @@ use crate::{ routes::{self, metrics}, services, types::{self, api, domain}, - utils::{self, OptionExt}, + utils::{OptionExt}, }; #[async_trait] @@ -124,12 +124,6 @@ fn build_apple_pay_session_request( apple_pay_merchant_cert: String, apple_pay_merchant_cert_key: String, ) -> RouterResult { - let applepay_session_request = types::RequestBody::log_and_get_request_body( - &request, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to encode ApplePay session request to a string of json")?; let mut url = state.conf.connectors.applepay.base_url.to_owned(); url.push_str("paymentservices/paymentSession"); @@ -142,7 +136,7 @@ fn build_apple_pay_session_request( headers::CONTENT_TYPE.to_string(), "application/json".to_string().into(), )]) - .body(Some(applepay_session_request)) + .set_body(RequestContent::Json(Box::new(request))) .add_certificate(Some(apple_pay_merchant_cert)) .add_certificate_key(Some(apple_pay_merchant_cert_key)) .build(); diff --git a/crates/router/src/core/verification.rs b/crates/router/src/core/verification.rs index e643e0455b8b..5674028a49ba 100644 --- a/crates/router/src/core/verification.rs +++ b/crates/router/src/core/verification.rs @@ -57,13 +57,6 @@ pub async fn verify_merchant_creds_for_applepay( partner_merchant_name: APPLEPAY_INTERNAL_MERCHANT_NAME.to_string(), }; - let applepay_req = types::RequestBody::log_and_get_request_body( - &request_body, - Encode::::encode_to_string_of_json, - ) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to encode ApplePay session request to a string of json")?; - let apple_pay_merch_verification_req = services::RequestBuilder::new() .method(services::Method::Post) .url(applepay_endpoint) @@ -72,7 +65,8 @@ pub async fn verify_merchant_creds_for_applepay( headers::CONTENT_TYPE.to_string(), "application/json".to_string().into(), )]) - .body(Some(applepay_req)) + .set_body(RequestContent::Json(Box::new(request_body))) + .add_certificate(Some(cert_data)) .add_certificate_key(Some(key_data)) .build(); diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index 9bbe35ba2a9d..7f36e0a0e95d 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -8,7 +8,7 @@ use api_models::{ payments::HeaderPayload, webhooks::{self, WebhookResponseTracker}, }; -use common_utils::{errors::ReportSwitchExt, events::ApiEventsType}; +use common_utils::{errors::ReportSwitchExt, events::ApiEventsType, request::RequestContent}; use error_stack::{report, IntoReport, ResultExt}; use masking::ExposeInterface; use router_env::{instrument, tracing, tracing_actix_web::RequestId}; @@ -28,15 +28,14 @@ use crate::{ events::api_logs::ApiEvent, logger, routes::{app::AppStateInfo, lock_utils, metrics::request::add_attributes, AppState}, - services::{self, authentication as auth}, + services::{self}, types::{ - self as router_types, api::{self, mandates::MandateResponseExt}, domain, storage::{self, enums}, transformers::{ForeignInto, ForeignTryInto}, }, - utils::{self as helper_utils, generate_id, Encode, OptionExt, ValueExt}, + utils::{generate_id, OptionExt, ValueExt}, }; const OUTGOING_WEBHOOK_TIMEOUT_SECS: u64 = 5; @@ -753,10 +752,10 @@ pub async fn create_event_and_trigger_outgoing_webhook( +pub async fn trigger_webhook_to_merchant<'a, W: types::OutgoingWebhookType + 'a>( business_profile: diesel_models::business_profile::BusinessProfile, webhook: api::OutgoingWebhook, - state: &AppState, + state: &'a AppState, ) -> CustomResult<(), errors::WebhooksFlowError> { let webhook_details_json = business_profile .webhook_details @@ -781,12 +780,12 @@ pub async fn trigger_webhook_to_merchant( let outgoing_webhooks_signature = transformed_outgoing_webhook .get_outgoing_webhooks_signature(business_profile.payment_response_hash_key.clone())?; - let transformed_outgoing_webhook_string = router_types::RequestBody::log_and_get_request_body( - &transformed_outgoing_webhook, - Encode::::encode_to_string_of_json, - ) - .change_context(errors::WebhooksFlowError::OutgoingWebhookEncodingFailed) - .attach_printable("There was an issue when encoding the outgoing webhook body")?; + // let transformed_outgoing_webhook_string = router_types::RequestBody::log_and_get_request_body( + // &transformed_outgoing_webhook, + // Encode::::encode_to_string_of_json, + // ) + // .change_context(errors::WebhooksFlowError::OutgoingWebhookEncodingFailed) + // .attach_printable("There was an issue when encoding the outgoing webhook body")?; let mut header = vec![( reqwest::header::CONTENT_TYPE.to_string(), @@ -802,7 +801,7 @@ pub async fn trigger_webhook_to_merchant( .url(&webhook_url) .attach_default_headers() .headers(header) - .body(Some(transformed_outgoing_webhook_string)) + .set_body(RequestContent::Json(Box::new(transformed_outgoing_webhook))) .build(); let response = state diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index b02808f70988..4dbe5fa562d3 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -135,7 +135,7 @@ pub trait ConnectorIntegration: ConnectorIntegrationAny, _connectors: &Connectors, ) -> CustomResult { - Ok(None) + Ok(RequestContent::Json(Box::new(json!(r#""#)))) } fn get_request_form_data( @@ -506,43 +506,32 @@ pub async fn send_request( Method::Get => client.get(url), Method::Post => { let client = client.post(url); - match request.content_type { - Some(ContentType::Json) => client.json(&request.payload), - - Some(ContentType::FormData) => { - client.multipart(request.form_data.unwrap_or_default()) - } - - // Currently this is not used remove this if not required - // If using this then handle the serde_part - Some(ContentType::FormUrlEncoded) => { - let payload = match request.payload.clone() { - Some(req) => serde_json::from_str(req.peek()) - .into_report() - .change_context(errors::ApiClientError::UrlEncodingFailed)?, - _ => json!(r#""#), - }; - let url_encoded_payload = serde_urlencoded::to_string(&payload) - .into_report() - .change_context(errors::ApiClientError::UrlEncodingFailed) - .attach_printable_lazy(|| { - format!( - "Unable to do url encoding on request: {:?}", - &request.payload - ) - })?; - - logger::debug!(?url_encoded_payload); - client.body(url_encoded_payload) - } - // If payload needs processing the body cannot have default - None => client.body(request.payload.expose_option().unwrap_or_default()), + match request.body { + Some(RequestContent::Json(payload)) => client.json(&payload), + Some(RequestContent::FormData(form)) => client.multipart(form), + Some(RequestContent::FormUrlEncoded(payload)) => client.form(&payload), + Some(RequestContent::Xml(payload)) => { + let body = quick_xml::se::to_string(&payload).into_report().change_context(errors::ApiClientError::BodySerializationFailed)?; + client.body(body) + .header("Content-Type", "application/xml") + }, + None => client, } } - - Method::Put => client - .put(url) - .body(request.payload.expose_option().unwrap_or_default()), // If payload needs processing the body cannot have default + Method::Put => { + let client = client.put(url); + match request.body { + Some(RequestContent::Json(payload)) => client.json(&payload), + Some(RequestContent::FormData(form)) => client.multipart(form), + Some(RequestContent::FormUrlEncoded(payload)) => client.form(&payload), + Some(RequestContent::Xml(payload)) => { + let body = quick_xml::se::to_string(&payload).into_report().change_context(errors::ApiClientError::BodySerializationFailed)?; + client.body(body) + .header("Content-Type", "application/xml") + }, + None => client, + } + }, Method::Delete => client.delete(url), } .add_headers(headers) From a8024938ca4a04f683693cd5829e67b05fb4401e Mon Sep 17 00:00:00 2001 From: Sampras lopes Date: Wed, 22 Nov 2023 23:07:51 +0530 Subject: [PATCH 04/19] feat(events): update request type for outgoing calls --- Cargo.lock | 15 +- crates/common_utils/src/request.rs | 12 +- crates/router/src/connector/aci.rs | 4 +- crates/router/src/connector/adyen.rs | 122 ++++++++-------- crates/router/src/connector/airwallex.rs | 11 +- .../router/src/connector/authorizedotnet.rs | 2 +- crates/router/src/connector/bambora.rs | 4 +- crates/router/src/connector/bankofamerica.rs | 7 +- crates/router/src/connector/bitpay.rs | 5 +- crates/router/src/connector/bluesnap.rs | 4 +- crates/router/src/connector/boku.rs | 8 +- crates/router/src/connector/braintree.rs | 16 +-- crates/router/src/connector/cashtocode.rs | 10 +- crates/router/src/connector/checkout.rs | 11 +- crates/router/src/connector/coinbase.rs | 5 +- crates/router/src/connector/cryptopay.rs | 11 +- crates/router/src/connector/cybersource.rs | 7 +- crates/router/src/connector/dlocal.rs | 6 +- crates/router/src/connector/dummyconnector.rs | 4 +- crates/router/src/connector/fiserv.rs | 7 +- crates/router/src/connector/forte.rs | 4 +- crates/router/src/connector/globalpay.rs | 6 +- crates/router/src/connector/globepay.rs | 8 +- crates/router/src/connector/gocardless.rs | 5 +- crates/router/src/connector/helcim.rs | 2 +- crates/router/src/connector/iatapay.rs | 5 +- crates/router/src/connector/klarna.rs | 4 +- crates/router/src/connector/mollie.rs | 4 +- crates/router/src/connector/multisafepay.rs | 4 +- crates/router/src/connector/nexinets.rs | 4 +- crates/router/src/connector/nmi.rs | 7 +- crates/router/src/connector/noon.rs | 5 +- crates/router/src/connector/nuvei.rs | 4 +- crates/router/src/connector/opayo.rs | 4 +- crates/router/src/connector/opennode.rs | 5 +- crates/router/src/connector/payeezy.rs | 7 +- crates/router/src/connector/payme.rs | 5 +- crates/router/src/connector/paypal.rs | 2 +- crates/router/src/connector/payu.rs | 4 +- crates/router/src/connector/powertranz.rs | 5 +- crates/router/src/connector/prophetpay.rs | 4 +- crates/router/src/connector/rapyd.rs | 22 ++- crates/router/src/connector/shift4.rs | 5 +- crates/router/src/connector/square.rs | 7 +- crates/router/src/connector/stax.rs | 9 +- crates/router/src/connector/stripe.rs | 12 +- .../src/connector/stripe/transformers.rs | 19 +-- crates/router/src/connector/trustpay.rs | 14 +- crates/router/src/connector/tsys.rs | 4 +- crates/router/src/connector/volt.rs | 4 +- crates/router/src/connector/wise.rs | 4 +- crates/router/src/connector/worldline.rs | 11 +- crates/router/src/connector/worldpay.rs | 5 +- crates/router/src/connector/zen.rs | 5 +- .../src/core/payment_methods/transformers.rs | 18 +-- .../src/core/payments/flows/session_flow.rs | 3 +- crates/router/src/core/verification.rs | 10 +- crates/router/src/core/webhooks.rs | 12 +- crates/router/src/core/webhooks/types.rs | 2 +- crates/router/src/routes/locker_migration.rs | 4 +- crates/router/src/services/api.rs | 135 +++++++++++------- 61 files changed, 341 insertions(+), 333 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 730b08774fa3..f2a4d717defe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1571,7 +1571,7 @@ dependencies = [ "once_cell", "phonenumber", "proptest", - "quick-xml", + "quick-xml 0.28.2", "rand 0.8.5", "regex", "reqwest", @@ -4183,7 +4183,7 @@ dependencies = [ "itertools 0.11.0", "lazy_static", "nom", - "quick-xml", + "quick-xml 0.28.2", "regex", "regex-cache", "serde", @@ -4430,6 +4430,16 @@ dependencies = [ "serde", ] +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quote" version = "1.0.33" @@ -4819,6 +4829,7 @@ dependencies = [ "once_cell", "openssl", "qrcode", + "quick-xml 0.31.0", "rand 0.8.5", "rand_chacha 0.3.1", "redis_interface", diff --git a/crates/common_utils/src/request.rs b/crates/common_utils/src/request.rs index 26fcf204aea3..e11a4dec5e40 100644 --- a/crates/common_utils/src/request.rs +++ b/crates/common_utils/src/request.rs @@ -57,9 +57,9 @@ impl std::fmt::Debug for RequestContent { pub enum RequestContent { Json(Box), - FormUrlEncoded(Box), + FormUrlEncoded(Box), FormData(reqwest::multipart::Form), - Xml(Box), + Xml(Box), } impl Request { @@ -195,7 +195,13 @@ impl RequestBody { pub fn get_inner_value(request_body: RequestContent) -> Secret { match request_body { - RequestContent::Json(i) | RequestContent::FormUrlEncoded(i) | RequestContent::Xml(i) => serde_json::to_string(&i.raw_serialize().unwrap_or(json!(""))).unwrap_or_default().into(), + RequestContent::Json(i) + | RequestContent::FormUrlEncoded(i) + | RequestContent::Xml(i) => { + serde_json::to_string(&i.raw_serialize().unwrap_or(json!(""))) + .unwrap_or_default() + .into() + } RequestContent::FormData(_) => String::new().into(), } } diff --git a/crates/router/src/connector/aci.rs b/crates/router/src/connector/aci.rs index bdc51fe3da0b..7e82da66c813 100644 --- a/crates/router/src/connector/aci.rs +++ b/crates/router/src/connector/aci.rs @@ -1,8 +1,8 @@ mod result_codes; pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as aci; @@ -21,7 +21,7 @@ use crate::{ self, api::{self, ConnectorCommon}, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/adyen.rs b/crates/router/src/connector/adyen.rs index 4f5f8553553b..a4e103e6e487 100644 --- a/crates/router/src/connector/adyen.rs +++ b/crates/router/src/connector/adyen.rs @@ -1,19 +1,19 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use api_models::webhooks::IncomingWebhookEvent; use base64::Engine; +use common_utils::request::RequestContent; use diesel_models::{enums as storage_enums, enums}; use error_stack::{IntoReport, ResultExt}; use ring::hmac; use router_env::{instrument, tracing}; use self::transformers as adyen; +use super::utils as connector_utils; use crate::{ configs::settings, - connector::utils as connector_utils, consts, core::{ self, @@ -31,7 +31,7 @@ use crate::{ domain, transformers::ForeignFrom, }, - utils::{self, crypto, ByteSliceExt, BytesExt, OptionExt}, + utils::{crypto, ByteSliceExt, BytesExt, OptionExt}, }; #[derive(Debug, Clone)] @@ -397,58 +397,48 @@ impl req: &types::RouterData, _connectors: &settings::Connectors, ) -> CustomResult { - - let encoded_data = req .request .encoded_data .clone() - .get_required_value("encoded_data")?; - let adyen_redirection_type = serde_urlencoded::from_str::< - transformers::AdyenRedirectRequestTypes, - >(encoded_data.as_str()) - .into_report() - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - - let connector_req = match adyen_redirection_type { - adyen::AdyenRedirectRequestTypes::AdyenRedirection(req) => { - adyen::AdyenRedirectRequest { - details: adyen::AdyenRedirectRequestTypes::AdyenRedirection( - adyen::AdyenRedirection { - redirect_result: req.redirect_result, - type_of_redirection_result: None, - result_code: None, - }, - ), - } - } - adyen::AdyenRedirectRequestTypes::AdyenThreeDS(req) => { - adyen::AdyenRedirectRequest { - details: adyen::AdyenRedirectRequestTypes::AdyenThreeDS( - adyen::AdyenThreeDS { - three_ds_result: req.three_ds_result, - type_of_redirection_result: None, - result_code: None, - }, - ), - } - } - adyen::AdyenRedirectRequestTypes::AdyenRefusal(req) => { - adyen::AdyenRedirectRequest { - details: adyen::AdyenRedirectRequestTypes::AdyenRefusal( - adyen::AdyenRefusal { - payload: req.payload, - type_of_redirection_result: None, - result_code: None, - }, - ), - } - } - }; - + .get_required_value("encoded_data") + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + let adyen_redirection_type = serde_urlencoded::from_str::< + transformers::AdyenRedirectRequestTypes, + >(encoded_data.as_str()) + .into_report() + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + let connector_req = match adyen_redirection_type { + adyen::AdyenRedirectRequestTypes::AdyenRedirection(req) => { + adyen::AdyenRedirectRequest { + details: adyen::AdyenRedirectRequestTypes::AdyenRedirection( + adyen::AdyenRedirection { + redirect_result: req.redirect_result, + type_of_redirection_result: None, + result_code: None, + }, + ), + } + } + adyen::AdyenRedirectRequestTypes::AdyenThreeDS(req) => adyen::AdyenRedirectRequest { + details: adyen::AdyenRedirectRequestTypes::AdyenThreeDS(adyen::AdyenThreeDS { + three_ds_result: req.three_ds_result, + type_of_redirection_result: None, + result_code: None, + }), + }, + adyen::AdyenRedirectRequestTypes::AdyenRefusal(req) => adyen::AdyenRedirectRequest { + details: adyen::AdyenRedirectRequestTypes::AdyenRefusal(adyen::AdyenRefusal { + payload: req.payload, + type_of_redirection_result: None, + result_code: None, + }), + }, + }; - Ok(RequestContent::Json(Box::new(connector_req))) - } + Ok(RequestContent::Json(Box::new(connector_req))) + } fn get_url( &self, @@ -476,22 +466,22 @@ impl .encoded_data .clone() .get_required_value("encoded_data") - .is_ok() { - Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) - .set_body(types::PaymentsSyncType::get_request_body( - self, req, connectors, - )?) - .build(), - )) - } else { - Ok(None) - } - + .is_ok() + { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .set_body(types::PaymentsSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } else { + Ok(None) + } } fn handle_response( diff --git a/crates/router/src/connector/airwallex.rs b/crates/router/src/connector/airwallex.rs index 2383eb4b802e..4a99398f1fb5 100644 --- a/crates/router/src/connector/airwallex.rs +++ b/crates/router/src/connector/airwallex.rs @@ -2,16 +2,18 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::{ext_traits::{ByteSliceExt, ValueExt}, request::RequestContent}; +use common_utils::{ + ext_traits::{ByteSliceExt, ValueExt}, + request::RequestContent, +}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as airwallex; -use super::utils::{AccessTokenRequestInfo, RefundsRequestData}; +use super::utils::{self as connector_utils, AccessTokenRequestInfo, RefundsRequestData}; use crate::{ configs::settings, - connector::utils as connector_utils, core::{ errors::{self, CustomResult}, payments, @@ -27,7 +29,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, RouterData, }, - utils::{self, crypto, BytesExt}, + utils::{crypto, BytesExt}, }; #[derive(Debug, Clone)] @@ -633,7 +635,6 @@ impl ConnectorIntegration CustomResult { let connector_req = airwallex::AirwallexPaymentsCaptureRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) } diff --git a/crates/router/src/connector/authorizedotnet.rs b/crates/router/src/connector/authorizedotnet.rs index 349bbe01e4b8..80cbd8b3a52a 100644 --- a/crates/router/src/connector/authorizedotnet.rs +++ b/crates/router/src/connector/authorizedotnet.rs @@ -21,7 +21,7 @@ use crate::{ self, api::{self, ConnectorCommon, ConnectorCommonExt, PaymentsCompleteAuthorize}, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/bambora.rs b/crates/router/src/connector/bambora.rs index 79931ed61782..f3953b15fe0f 100644 --- a/crates/router/src/connector/bambora.rs +++ b/crates/router/src/connector/bambora.rs @@ -1,8 +1,8 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use transformers as bambora; @@ -29,7 +29,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/bankofamerica.rs b/crates/router/src/connector/bankofamerica.rs index b90f5b59318b..a6183db7c2ef 100644 --- a/crates/router/src/connector/bankofamerica.rs +++ b/crates/router/src/connector/bankofamerica.rs @@ -1,9 +1,9 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use base64::Engine; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, PeekInterface}; @@ -28,7 +28,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; pub const V_C_MERCHANT_ID: &str = "v-c-merchant-id"; @@ -135,7 +135,8 @@ where .skip(base_url.len() - 1) .collect(); let sha256 = self.generate_digest( - types::RequestBody::get_inner_value( boa_req).expose() + types::RequestBody::get_inner_value(boa_req) + .expose() .as_bytes(), ); let signature = self.generate_signature( diff --git a/crates/router/src/connector/bitpay.rs b/crates/router/src/connector/bitpay.rs index 66e634a76bac..225812ca2d68 100644 --- a/crates/router/src/connector/bitpay.rs +++ b/crates/router/src/connector/bitpay.rs @@ -1,9 +1,8 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; -use common_utils::{errors::ReportSwitchExt, ext_traits::ByteSliceExt}; +use common_utils::{errors::ReportSwitchExt, ext_traits::ByteSliceExt, request::RequestContent}; use error_stack::ResultExt; use masking::PeekInterface; use transformers as bitpay; @@ -24,7 +23,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/bluesnap.rs b/crates/router/src/connector/bluesnap.rs index 7c3177c9997f..53072fc3083d 100644 --- a/crates/router/src/connector/bluesnap.rs +++ b/crates/router/src/connector/bluesnap.rs @@ -1,12 +1,12 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use base64::Engine; use common_utils::{ crypto, ext_traits::{StringExt, ValueExt}, + request::RequestContent, }; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; @@ -35,7 +35,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; pub const BLUESNAP_TRANSACTION_NOT_FOUND: &str = "is not authorized to view merchant-transaction:"; diff --git a/crates/router/src/connector/boku.rs b/crates/router/src/connector/boku.rs index 7a96d73211e6..caae284be16a 100644 --- a/crates/router/src/connector/boku.rs +++ b/crates/router/src/connector/boku.rs @@ -1,9 +1,7 @@ pub mod transformers; - use std::fmt::Debug; -use common_utils::request::RequestContent; -use common_utils::ext_traits::XmlExt; +use common_utils::{ext_traits::XmlExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, Report, ResultExt}; use masking::{ExposeInterface, PeekInterface, Secret, WithType}; @@ -12,9 +10,9 @@ use roxmltree; use time::OffsetDateTime; use transformers as boku; +use super::utils as connector_utils; use crate::{ configs::settings, - connector::utils as connector_utils, consts, core::errors::{self, CustomResult}, headers, logger, @@ -29,7 +27,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt, OptionExt}, + utils::{BytesExt, OptionExt}, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/braintree.rs b/crates/router/src/connector/braintree.rs index 8fb090072bf6..b5f29a5b739a 100644 --- a/crates/router/src/connector/braintree.rs +++ b/crates/router/src/connector/braintree.rs @@ -12,10 +12,9 @@ use ring::hmac; use sha1::{Digest, Sha1}; use self::transformers as braintree; -use super::utils::PaymentsAuthorizeRequestData; +use super::utils::{self as connector_utils, PaymentsAuthorizeRequestData}; use crate::{ configs::settings, - connector::utils as connector_utils, consts, core::{ errors::{self, CustomResult}, @@ -324,8 +323,7 @@ impl req: &types::TokenizationRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_req = - braintree_graphql_transformers::BraintreeTokenRequest::try_from(req)?; + let connector_req = braintree_graphql_transformers::BraintreeTokenRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -770,11 +768,11 @@ impl ConnectorIntegration { let connector_req = braintree::BraintreePaymentsRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::Json(Box::new(connector_req))) } } } @@ -922,7 +920,7 @@ impl ConnectorIntegration { - let connector_req= + let connector_req = braintree_graphql_transformers::BraintreeCancelRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -1176,7 +1174,7 @@ impl ConnectorIntegration { let connector_req = braintree_graphql_transformers::BraintreeRSyncRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::Json(Box::new(connector_req))) } false => Err(errors::ConnectorError::RequestEncodingFailed).into_report(), } @@ -1570,7 +1568,7 @@ impl let connector_api_version = &req.connector_api_version; match self.is_braintree_graphql_version(connector_api_version) { true => { - let connector_req= + let connector_req = braintree_graphql_transformers::BraintreePaymentsRequest::try_from( &connector_router_data, )?; diff --git a/crates/router/src/connector/cashtocode.rs b/crates/router/src/connector/cashtocode.rs index ac8e6c8568ce..d01059ef22c7 100644 --- a/crates/router/src/connector/cashtocode.rs +++ b/crates/router/src/connector/cashtocode.rs @@ -1,17 +1,16 @@ pub mod transformers; - use std::fmt::Debug; -use common_utils::request::RequestContent; use base64::Engine; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::{PeekInterface, Secret}; use transformers as cashtocode; +use super::utils as connector_utils; use crate::{ configs::settings::{self}, - connector::{utils as connector_utils, utils as conn_utils}, core::errors::{self, CustomResult}, headers, services::{ @@ -24,7 +23,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, domain, storage, ErrorResponse, Response, }, - utils::{self, ByteSliceExt, BytesExt}, + utils::{ByteSliceExt, BytesExt}, }; #[derive(Debug, Clone)] @@ -327,7 +326,8 @@ impl api::IncomingWebhook for Cashtocode { request: &api::IncomingWebhookRequestDetails<'_>, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { - let base64_signature = conn_utils::get_header_key_value("authorization", request.headers)?; + let base64_signature = + connector_utils::get_header_key_value("authorization", request.headers)?; let signature = base64_signature.as_bytes().to_owned(); Ok(signature) } diff --git a/crates/router/src/connector/checkout.rs b/crates/router/src/connector/checkout.rs index 0620e4a30843..8d5715a91aa0 100644 --- a/crates/router/src/connector/checkout.rs +++ b/crates/router/src/connector/checkout.rs @@ -1,9 +1,8 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; -use common_utils::{crypto, ext_traits::ByteSliceExt}; +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; @@ -30,7 +29,8 @@ use crate::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, }, - utils::{self, BytesExt}, + utils, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -952,7 +952,6 @@ impl ConnectorIntegration, connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { - let api_method; - let body = types::RequestBody::get_inner_value(self.get_request_body(req, connectors)?).peek().to_owned(); - api_method = "POST".to_string(); + let api_method = "POST".to_string(); + let body = types::RequestBody::get_inner_value(self.get_request_body(req, connectors)?) + .peek() + .to_owned(); let md5_payload = crypto::Md5 .generate_digest(body.as_bytes()) .change_context(errors::ConnectorError::RequestEncodingFailed)?; diff --git a/crates/router/src/connector/cybersource.rs b/crates/router/src/connector/cybersource.rs index 85dea3cef667..4a6952cb0991 100644 --- a/crates/router/src/connector/cybersource.rs +++ b/crates/router/src/connector/cybersource.rs @@ -1,9 +1,9 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use base64::Engine; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, PeekInterface}; @@ -27,7 +27,7 @@ use crate::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -184,7 +184,8 @@ where .skip(base_url.len() - 1) .collect(); let sha256 = self.generate_digest( - types::RequestBody::get_inner_value(cybersource_req).expose() + types::RequestBody::get_inner_value(cybersource_req) + .expose() .as_bytes(), ); let http_method = self.get_http_method(); diff --git a/crates/router/src/connector/dlocal.rs b/crates/router/src/connector/dlocal.rs index 0ab73e370ad7..3e45c0b7141a 100644 --- a/crates/router/src/connector/dlocal.rs +++ b/crates/router/src/connector/dlocal.rs @@ -1,11 +1,11 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use common_utils::{ crypto::{self, SignMessage}, date_time, + request::RequestContent, }; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; @@ -28,7 +28,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -58,7 +58,7 @@ where ) -> CustomResult)>, errors::ConnectorError> { let dlocal_req = self.get_request_body(req, connectors)?; - + let date = date_time::date_as_yyyymmddthhmmssmmmz() .into_report() .change_context(errors::ConnectorError::RequestEncodingFailed)?; diff --git a/crates/router/src/connector/dummyconnector.rs b/crates/router/src/connector/dummyconnector.rs index b06bd819aa7e..677e87aea7fd 100644 --- a/crates/router/src/connector/dummyconnector.rs +++ b/crates/router/src/connector/dummyconnector.rs @@ -1,8 +1,8 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; @@ -22,7 +22,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/fiserv.rs b/crates/router/src/connector/fiserv.rs index 1de8bd843aec..f6074c0f19d7 100644 --- a/crates/router/src/connector/fiserv.rs +++ b/crates/router/src/connector/fiserv.rs @@ -1,9 +1,9 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use base64::Engine; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, PeekInterface}; @@ -28,7 +28,7 @@ use crate::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -70,8 +70,7 @@ where fiserv::FiservAuthType::try_from(&req.connector_auth_type)?; let mut auth_header = self.get_auth_header(&req.connector_auth_type)?; - let fiserv_req = self - .get_request_body(req, connectors)?; + let fiserv_req = self.get_request_body(req, connectors)?; let client_request_id = Uuid::new_v4().to_string(); let hmac = self diff --git a/crates/router/src/connector/forte.rs b/crates/router/src/connector/forte.rs index 427ad828dbd3..7a0b50b827d9 100644 --- a/crates/router/src/connector/forte.rs +++ b/crates/router/src/connector/forte.rs @@ -1,9 +1,9 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use base64::Engine; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; @@ -28,7 +28,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/globalpay.rs b/crates/router/src/connector/globalpay.rs index 276bd2c87875..d890807a621e 100644 --- a/crates/router/src/connector/globalpay.rs +++ b/crates/router/src/connector/globalpay.rs @@ -1,4 +1,5 @@ mod requests; +use super::utils as connector_utils; mod response; pub mod transformers; @@ -20,7 +21,6 @@ use self::{ use super::utils::RefundsRequestData; use crate::{ configs::settings, - connector::{utils as connector_utils, utils as conn_utils}, core::{ errors::{self, CustomResult}, payments, @@ -36,7 +36,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt, PaymentsCompleteAuthorize}, ErrorResponse, }, - utils::{self, crypto, BytesExt}, + utils::{crypto, BytesExt}, }; #[derive(Debug, Clone)] @@ -847,7 +847,7 @@ impl api::IncomingWebhook for Globalpay { request: &api::IncomingWebhookRequestDetails<'_>, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { - let signature = conn_utils::get_header_key_value("x-gp-signature", request.headers)?; + let signature = connector_utils::get_header_key_value("x-gp-signature", request.headers)?; Ok(signature.as_bytes().to_vec()) } diff --git a/crates/router/src/connector/globepay.rs b/crates/router/src/connector/globepay.rs index a1c8018982fc..179344b6d70c 100644 --- a/crates/router/src/connector/globepay.rs +++ b/crates/router/src/connector/globepay.rs @@ -1,9 +1,11 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; -use common_utils::crypto::{self, GenerateDigest}; +use common_utils::{ + crypto::{self, GenerateDigest}, + request::RequestContent, +}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use hex::encode; @@ -23,7 +25,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/gocardless.rs b/crates/router/src/connector/gocardless.rs index 0effb270c7b3..3fa7b961dc4c 100644 --- a/crates/router/src/connector/gocardless.rs +++ b/crates/router/src/connector/gocardless.rs @@ -1,10 +1,9 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use api_models::enums::enums; -use common_utils::{crypto, ext_traits::ByteSliceExt}; +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as gocardless; @@ -24,7 +23,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/helcim.rs b/crates/router/src/connector/helcim.rs index 20388f9c8b96..aa4b671d69b1 100644 --- a/crates/router/src/connector/helcim.rs +++ b/crates/router/src/connector/helcim.rs @@ -1,8 +1,8 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::ExposeInterface; diff --git a/crates/router/src/connector/iatapay.rs b/crates/router/src/connector/iatapay.rs index 071f08032f5f..a6316649b2e3 100644 --- a/crates/router/src/connector/iatapay.rs +++ b/crates/router/src/connector/iatapay.rs @@ -1,10 +1,9 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use base64::Engine; -use common_utils::{crypto, ext_traits::ByteSliceExt}; +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as iatapay; @@ -26,7 +25,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{BytesExt, Encode}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/klarna.rs b/crates/router/src/connector/klarna.rs index cad4449abb79..ba3927fde07d 100644 --- a/crates/router/src/connector/klarna.rs +++ b/crates/router/src/connector/klarna.rs @@ -1,8 +1,8 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use api_models::payments as api_payments; +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use transformers as klarna; @@ -22,7 +22,7 @@ use crate::{ api::{self, ConnectorCommon}, storage::enums as storage_enums, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/mollie.rs b/crates/router/src/connector/mollie.rs index c9f97caed17f..1522fa576968 100644 --- a/crates/router/src/connector/mollie.rs +++ b/crates/router/src/connector/mollie.rs @@ -1,8 +1,8 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as mollie; @@ -25,7 +25,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/multisafepay.rs b/crates/router/src/connector/multisafepay.rs index 18844641c068..28e68cd8252b 100644 --- a/crates/router/src/connector/multisafepay.rs +++ b/crates/router/src/connector/multisafepay.rs @@ -1,8 +1,8 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use masking::ExposeInterface; use transformers as multisafepay; @@ -22,7 +22,7 @@ use crate::{ storage::enums, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/nexinets.rs b/crates/router/src/connector/nexinets.rs index 1061491cc0a9..a3a9a8e26241 100644 --- a/crates/router/src/connector/nexinets.rs +++ b/crates/router/src/connector/nexinets.rs @@ -1,8 +1,8 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use transformers as nexinets; @@ -25,7 +25,7 @@ use crate::{ storage::enums, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/nmi.rs b/crates/router/src/connector/nmi.rs index eca55e7fad92..f3c8af5f6058 100644 --- a/crates/router/src/connector/nmi.rs +++ b/crates/router/src/connector/nmi.rs @@ -1,17 +1,15 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; -use common_utils::ext_traits::ByteSliceExt; +use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use transformers as nmi; -use self::transformers::NmiCaptureRequest; +use super::utils as connector_utils; use crate::{ configs::settings, - connector::utils as connector_utils, core::errors::{self, CustomResult}, services::{self, request, ConnectorIntegration, ConnectorValidation}, types::{ @@ -19,7 +17,6 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, }, - utils, }; #[derive(Clone, Debug)] diff --git a/crates/router/src/connector/noon.rs b/crates/router/src/connector/noon.rs index 9a3342ae0cc9..15dfb967f8d4 100644 --- a/crates/router/src/connector/noon.rs +++ b/crates/router/src/connector/noon.rs @@ -1,10 +1,9 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use base64::Engine; -use common_utils::{crypto, ext_traits::ByteSliceExt}; +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; @@ -29,7 +28,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/nuvei.rs b/crates/router/src/connector/nuvei.rs index ab380966f552..6ab0033118e8 100644 --- a/crates/router/src/connector/nuvei.rs +++ b/crates/router/src/connector/nuvei.rs @@ -4,9 +4,9 @@ use std::fmt::Debug; use ::common_utils::{ crypto, - request::RequestContent, errors::ReportSwitchExt, ext_traits::{BytesExt, ValueExt}, + request::RequestContent, }; use error_stack::{IntoReport, ResultExt}; use transformers as nuvei; @@ -26,7 +26,7 @@ use crate::{ storage::enums, ErrorResponse, Response, }, - utils::{self as common_utils, ByteSliceExt}, + utils::ByteSliceExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/opayo.rs b/crates/router/src/connector/opayo.rs index e544120aff94..fe48737ef6e6 100644 --- a/crates/router/src/connector/opayo.rs +++ b/crates/router/src/connector/opayo.rs @@ -1,8 +1,8 @@ mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::ExposeInterface; @@ -23,7 +23,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/opennode.rs b/crates/router/src/connector/opennode.rs index 4d85c2eefad9..e3429866b94a 100644 --- a/crates/router/src/connector/opennode.rs +++ b/crates/router/src/connector/opennode.rs @@ -1,9 +1,8 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; -use common_utils::crypto; +use common_utils::{crypto, request::RequestContent}; use error_stack::{IntoReport, ResultExt}; use transformers as opennode; @@ -23,7 +22,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{BytesExt, Encode}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/payeezy.rs b/crates/router/src/connector/payeezy.rs index 19f788cb8848..70199aee5445 100644 --- a/crates/router/src/connector/payeezy.rs +++ b/crates/router/src/connector/payeezy.rs @@ -1,9 +1,9 @@ mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use base64::Engine; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::ExposeInterface; @@ -27,7 +27,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -43,7 +43,8 @@ where connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { let auth = payeezy::PayeezyAuthType::try_from(&req.connector_auth_type)?; - let request_payload = types::RequestBody::get_inner_value(self.get_request_body(req, connectors)?).expose(); + let request_payload = + types::RequestBody::get_inner_value(self.get_request_body(req, connectors)?).expose(); let timestamp = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .ok() diff --git a/crates/router/src/connector/payme.rs b/crates/router/src/connector/payme.rs index 12cac7d6b4f9..a2ac9fe923e3 100644 --- a/crates/router/src/connector/payme.rs +++ b/crates/router/src/connector/payme.rs @@ -1,10 +1,9 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use api_models::enums::AuthenticationType; -use common_utils::crypto; +use common_utils::{crypto, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::ExposeInterface; @@ -24,7 +23,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, domain, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/paypal.rs b/crates/router/src/connector/paypal.rs index 48ec9b8e76ac..2a8a45181472 100644 --- a/crates/router/src/connector/paypal.rs +++ b/crates/router/src/connector/paypal.rs @@ -33,7 +33,7 @@ use crate::{ transformers::ForeignFrom, ConnectorAuthType, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/payu.rs b/crates/router/src/connector/payu.rs index 3c59bf9921c7..4ea69b6a7fc0 100644 --- a/crates/router/src/connector/payu.rs +++ b/crates/router/src/connector/payu.rs @@ -1,8 +1,8 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; @@ -23,7 +23,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/powertranz.rs b/crates/router/src/connector/powertranz.rs index 99099d50c99b..afea365145fe 100644 --- a/crates/router/src/connector/powertranz.rs +++ b/crates/router/src/connector/powertranz.rs @@ -1,10 +1,9 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use api_models::enums::AuthenticationType; -use common_utils::ext_traits::ValueExt; +use common_utils::{ext_traits::ValueExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::ExposeInterface; @@ -28,7 +27,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/prophetpay.rs b/crates/router/src/connector/prophetpay.rs index 811bf79eb265..d749842356e2 100644 --- a/crates/router/src/connector/prophetpay.rs +++ b/crates/router/src/connector/prophetpay.rs @@ -1,9 +1,9 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use base64::Engine; +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as prophetpay; @@ -23,7 +23,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/rapyd.rs b/crates/router/src/connector/rapyd.rs index b9230531b746..76a21d30640e 100644 --- a/crates/router/src/connector/rapyd.rs +++ b/crates/router/src/connector/rapyd.rs @@ -1,9 +1,8 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use base64::Engine; -use common_utils::{date_time, ext_traits::StringExt}; +use common_utils::{date_time, ext_traits::StringExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, Report, ResultExt}; use masking::{ExposeInterface, PeekInterface}; @@ -11,9 +10,9 @@ use rand::distributions::{Alphanumeric, DistString}; use ring::hmac; use transformers as rapyd; +use super::utils as connector_utils; use crate::{ configs::settings, - connector::{utils as connector_utils, utils as conn_utils}, consts, core::errors::{self, CustomResult}, headers, logger, @@ -209,8 +208,7 @@ impl let salt = Alphanumeric.sample_string(&mut rand::thread_rng(), 12); let auth: rapyd::RapydAuthType = rapyd::RapydAuthType::try_from(&req.connector_auth_type)?; - let body = types::PaymentsAuthorizeType::get_request_body(self, req, connectors)? - .ok_or(errors::ConnectorError::RequestEncodingFailed)?; + let body = types::PaymentsAuthorizeType::get_request_body(self, req, connectors)?; let req_body = types::RequestBody::get_inner_value(body).expose(); let signature = self.generate_signature(&auth, "post", "/v1/payments", &req_body, ×tamp, &salt)?; @@ -517,8 +515,7 @@ impl "/v1/payments/{}/capture", req.request.connector_transaction_id ); - let body = types::PaymentsCaptureType::get_request_body(self, req, connectors)? - .ok_or(errors::ConnectorError::RequestEncodingFailed)?; + let body = types::PaymentsCaptureType::get_request_body(self, req, connectors)?; let req_body = types::RequestBody::get_inner_value(body).expose(); let signature = self.generate_signature(&auth, "post", &url_path, &req_body, ×tamp, &salt)?; @@ -649,8 +646,7 @@ impl services::ConnectorIntegration, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { - let base64_signature = conn_utils::get_header_key_value("signature", request.headers)?; + let base64_signature = connector_utils::get_header_key_value("signature", request.headers)?; let signature = consts::BASE64_ENGINE_URL_SAFE .decode(base64_signature.as_bytes()) .into_report() @@ -750,11 +746,11 @@ impl api::IncomingWebhook for Rapyd { merchant_id: &str, connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { - let host = conn_utils::get_header_key_value("host", request.headers)?; + let host = connector_utils::get_header_key_value("host", request.headers)?; let connector = self.id(); let url_path = format!("https://{host}/webhooks/{merchant_id}/{connector}"); - let salt = conn_utils::get_header_key_value("salt", request.headers)?; - let timestamp = conn_utils::get_header_key_value("timestamp", request.headers)?; + let salt = connector_utils::get_header_key_value("salt", request.headers)?; + let timestamp = connector_utils::get_header_key_value("timestamp", request.headers)?; let stringify_auth = String::from_utf8(connector_webhook_secrets.secret.to_vec()) .into_report() .change_context(errors::ConnectorError::WebhookSourceVerificationFailed) diff --git a/crates/router/src/connector/shift4.rs b/crates/router/src/connector/shift4.rs index 39dccbdc19ef..b2ea8110dc2e 100644 --- a/crates/router/src/connector/shift4.rs +++ b/crates/router/src/connector/shift4.rs @@ -1,9 +1,8 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; -use common_utils::ext_traits::ByteSliceExt; +use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use transformers as shift4; @@ -27,7 +26,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/square.rs b/crates/router/src/connector/square.rs index 1f2dbe0832ca..455580b82376 100644 --- a/crates/router/src/connector/square.rs +++ b/crates/router/src/connector/square.rs @@ -1,11 +1,10 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use api_models::enums; use base64::Engine; -use common_utils::ext_traits::ByteSliceExt; +use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as square; @@ -29,7 +28,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -249,7 +248,7 @@ impl req: &types::TokenizationRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_req = square::SquareTokenRequest::try_from(req)?; + let connector_req = square::SquareTokenRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) } diff --git a/crates/router/src/connector/stax.rs b/crates/router/src/connector/stax.rs index 85d5b5ae683d..a684264062e0 100644 --- a/crates/router/src/connector/stax.rs +++ b/crates/router/src/connector/stax.rs @@ -1,9 +1,8 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; -use common_utils::ext_traits::ByteSliceExt; +use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; @@ -26,7 +25,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, domain, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -164,7 +163,7 @@ impl req: &types::ConnectorCustomerRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_req = stax::StaxCustomerRequest::try_from(req)?; + let connector_req = stax::StaxCustomerRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -251,7 +250,7 @@ impl req: &types::TokenizationRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_req = stax::StaxTokenRequest::try_from(req)?; + let connector_req = stax::StaxTokenRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) } diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index 8855ae7d0cd1..681ecb64d06f 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -27,7 +27,7 @@ use crate::{ self, api::{self, ConnectorCommon}, }, - utils::{self, crypto, ByteSliceExt, BytesExt, OptionExt}, + utils::{crypto, ByteSliceExt, BytesExt, OptionExt}, }; #[derive(Debug, Clone)] @@ -1458,8 +1458,8 @@ impl req: &types::UploadFileRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_req = transformers::construct_file_upload_request(req.clone())?; - Ok(RequestContent::FormUrlEncoded(connector_req)) + let connector_req = transformers::construct_file_upload_request(req.clone())?; + Ok(RequestContent::FormData(connector_req)) } fn build_request( @@ -1473,7 +1473,9 @@ impl .url(&types::UploadFileType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::UploadFileType::get_headers(self, req, connectors)?) - .set_body(types::UploadFileType::get_request_body(self, req, connectors)) + .set_body(types::UploadFileType::get_request_body( + self, req, connectors, + )?) .build(), )) } @@ -1684,7 +1686,7 @@ impl req: &types::SubmitEvidenceRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_req = stripe::Evidence::try_from(req)?; + let connector_req = stripe::Evidence::try_from(req)?; Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 3f0d4f543ba4..61315124c3ed 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -5,6 +5,7 @@ use common_utils::{ errors::CustomResult, ext_traits::{ByteSliceExt, BytesExt}, pii::{self, Email}, + request::RequestContent, }; use data_models::mandates::AcceptanceType; use error_stack::{IntoReport, ResultExt}; @@ -24,7 +25,7 @@ use crate::{ storage::enums, transformers::{ForeignFrom, ForeignTryFrom}, }, - utils::{self, OptionExt}, + utils::OptionExt, }; pub struct StripeAuthType { @@ -3425,26 +3426,16 @@ pub struct StripeGpayToken { pub fn get_bank_transfer_request_data( req: &types::PaymentsAuthorizeRouterData, bank_transfer_data: &api_models::payments::BankTransferData, -) -> CustomResult, errors::ConnectorError> { +) -> CustomResult { match bank_transfer_data { api_models::payments::BankTransferData::AchBankTransfer { .. } | api_models::payments::BankTransferData::MultibancoBankTransfer { .. } => { let req = ChargesRequest::try_from(req)?; - let request = types::RequestBody::log_and_get_request_body( - &req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(request)) + Ok(RequestContent::FormUrlEncoded(Box::new(req))) } _ => { let req = PaymentIntentRequest::try_from(req)?; - let request = types::RequestBody::log_and_get_request_body( - &req, - utils::Encode::::url_encode, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(request)) + Ok(RequestContent::FormUrlEncoded(Box::new(req))) } } } diff --git a/crates/router/src/connector/trustpay.rs b/crates/router/src/connector/trustpay.rs index 1dc1c2bf1f61..47ff0794fda6 100644 --- a/crates/router/src/connector/trustpay.rs +++ b/crates/router/src/connector/trustpay.rs @@ -1,10 +1,11 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use base64::Engine; -use common_utils::{crypto, errors::ReportSwitchExt, ext_traits::ByteSliceExt}; +use common_utils::{ + crypto, errors::ReportSwitchExt, ext_traits::ByteSliceExt, request::RequestContent, +}; use error_stack::{IntoReport, Report, ResultExt}; use masking::PeekInterface; use transformers as trustpay; @@ -563,11 +564,12 @@ impl ConnectorIntegration { + Ok(RequestContent::Json(Box::new(connector_req))) } - }; - Ok(RequestContent::Json(Box::new(connector_req))) + _ => Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))), + } } fn build_request( @@ -669,7 +671,7 @@ impl ConnectorIntegration { Ok(RequestContent::Json(Box::new(connector_req))) } - _ => Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) + _ => Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))), } } diff --git a/crates/router/src/connector/tsys.rs b/crates/router/src/connector/tsys.rs index 29622d0c3c42..d765729fade3 100644 --- a/crates/router/src/connector/tsys.rs +++ b/crates/router/src/connector/tsys.rs @@ -1,8 +1,8 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; +use common_utils::request::RequestContent; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use transformers as tsys; @@ -22,7 +22,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/volt.rs b/crates/router/src/connector/volt.rs index e51cbd8da811..581d978165ba 100644 --- a/crates/router/src/connector/volt.rs +++ b/crates/router/src/connector/volt.rs @@ -1,8 +1,8 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, PeekInterface}; use transformers as volt; @@ -21,7 +21,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/wise.rs b/crates/router/src/connector/wise.rs index e33c9a6f4fdb..18c2e7bfef38 100644 --- a/crates/router/src/connector/wise.rs +++ b/crates/router/src/connector/wise.rs @@ -1,7 +1,7 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; #[cfg(feature = "payouts")] use masking::PeekInterface; @@ -25,7 +25,7 @@ use crate::{ utils::BytesExt, }; #[cfg(feature = "payouts")] -use crate::{core::payments, routes, utils}; +use crate::{core::payments, routes}; #[derive(Debug, Clone)] pub struct Wise; diff --git a/crates/router/src/connector/worldline.rs b/crates/router/src/connector/worldline.rs index a94781bf3ef5..c9e0c05eadf6 100644 --- a/crates/router/src/connector/worldline.rs +++ b/crates/router/src/connector/worldline.rs @@ -1,10 +1,9 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; use base64::Engine; -use common_utils::ext_traits::ByteSliceExt; +use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, PeekInterface}; @@ -12,10 +11,9 @@ use ring::hmac; use time::{format_description, OffsetDateTime}; use transformers as worldline; -use super::utils::RefundsRequestData; +use super::utils::{self as connector_utils, RefundsRequestData}; use crate::{ configs::settings::Connectors, - connector::{utils as connector_utils, utils as conn_utils}, consts, core::errors::{self, CustomResult}, headers, logger, @@ -29,7 +27,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, }, - utils::{self, crypto, BytesExt, OptionExt}, + utils::{crypto, BytesExt, OptionExt}, }; #[derive(Debug, Clone)] @@ -733,7 +731,8 @@ impl api::IncomingWebhook for Worldline { request: &api::IncomingWebhookRequestDetails<'_>, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { - let header_value = conn_utils::get_header_key_value("X-GCS-Signature", request.headers)?; + let header_value = + connector_utils::get_header_key_value("X-GCS-Signature", request.headers)?; let signature = consts::BASE64_ENGINE .decode(header_value.as_bytes()) .into_report() diff --git a/crates/router/src/connector/worldpay.rs b/crates/router/src/connector/worldpay.rs index be81a828ca01..efa7a3d7308f 100644 --- a/crates/router/src/connector/worldpay.rs +++ b/crates/router/src/connector/worldpay.rs @@ -3,9 +3,8 @@ mod response; pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; -use common_utils::{crypto, ext_traits::ByteSliceExt}; +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use transformers as worldpay; @@ -27,7 +26,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self as ext_traits, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/zen.rs b/crates/router/src/connector/zen.rs index 9e1f4bc751eb..05a539208588 100644 --- a/crates/router/src/connector/zen.rs +++ b/crates/router/src/connector/zen.rs @@ -1,9 +1,8 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::request::RequestContent; -use common_utils::{crypto, ext_traits::ByteSliceExt}; +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; use transformers as zen; @@ -28,7 +27,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, domain, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs index 0c88efe19789..71c2cf8f2003 100644 --- a/crates/router/src/core/payment_methods/transformers.rs +++ b/crates/router/src/core/payment_methods/transformers.rs @@ -97,7 +97,7 @@ pub struct DeleteCardResp { #[serde(rename_all = "camelCase")] pub struct AddCardRequest<'a> { pub card_number: cards::CardNumber, - pub customer_id: &'a str, + pub customer_id: String, pub card_exp_month: Secret, pub card_exp_year: Secret, pub merchant_id: &'a str, @@ -400,7 +400,7 @@ pub fn mk_add_card_request( card: &api::CardDetail, customer_id: &str, _req: &api::PaymentMethodCreate, - locker_id: &str, + locker_id: &'static str, merchant_id: &str, ) -> CustomResult { let customer_id = if cfg!(feature = "release") { @@ -410,7 +410,7 @@ pub fn mk_add_card_request( }; let add_card_req = AddCardRequest { card_number: card.card_number.clone(), - customer_id: &customer_id, + customer_id, card_exp_month: card.card_exp_month.clone(), card_exp_year: card.card_exp_year.clone(), merchant_id: locker_id, @@ -473,10 +473,10 @@ pub async fn mk_get_card_request_hs( Ok(request) } -pub fn mk_get_card_request<'a>( +pub fn mk_get_card_request( locker: &settings::Locker, - locker_id: &'a str, - card_id: &'a str, + locker_id: &'static str, + card_id: &'static str, ) -> CustomResult { let get_card_req = GetCard { merchant_id: locker_id, @@ -547,10 +547,10 @@ pub async fn mk_delete_card_request_hs( Ok(request) } -pub fn mk_delete_card_request<'a>( +pub fn mk_delete_card_request( locker: &settings::Locker, - merchant_id: &'a str, - card_id: &'a str, + merchant_id: &'static str, + card_id: &'static str, ) -> CustomResult { let delete_card_req = GetCard { merchant_id, diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index ca4d693b3c59..91513019179f 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -15,7 +15,7 @@ use crate::{ routes::{self, metrics}, services, types::{self, api, domain}, - utils::{OptionExt}, + utils::OptionExt, }; #[async_trait] @@ -124,7 +124,6 @@ fn build_apple_pay_session_request( apple_pay_merchant_cert: String, apple_pay_merchant_cert_key: String, ) -> RouterResult { - let mut url = state.conf.connectors.applepay.base_url.to_owned(); url.push_str("paymentservices/paymentSession"); diff --git a/crates/router/src/core/verification.rs b/crates/router/src/core/verification.rs index 5674028a49ba..bac47b34dced 100644 --- a/crates/router/src/core/verification.rs +++ b/crates/router/src/core/verification.rs @@ -1,16 +1,11 @@ pub mod utils; use api_models::verifications::{self, ApplepayMerchantResponse}; -use common_utils::{errors::CustomResult, ext_traits::Encode}; +use common_utils::{errors::CustomResult, request::RequestContent}; use error_stack::ResultExt; #[cfg(feature = "kms")] use external_services::kms; -use crate::{ - core::errors::{self, api_error_response}, - headers, logger, - routes::AppState, - services, types, -}; +use crate::{core::errors::api_error_response, headers, logger, routes::AppState, services}; const APPLEPAY_INTERNAL_MERCHANT_NAME: &str = "Applepay_merchant"; @@ -66,7 +61,6 @@ pub async fn verify_merchant_creds_for_applepay( "application/json".to_string().into(), )]) .set_body(RequestContent::Json(Box::new(request_body))) - .add_certificate(Some(cert_data)) .add_certificate_key(Some(key_data)) .build(); diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index 7f36e0a0e95d..a4dcec9b5075 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -28,14 +28,14 @@ use crate::{ events::api_logs::ApiEvent, logger, routes::{app::AppStateInfo, lock_utils, metrics::request::add_attributes, AppState}, - services::{self}, + services::{self, authentication as auth}, types::{ api::{self, mandates::MandateResponseExt}, domain, storage::{self, enums}, transformers::{ForeignInto, ForeignTryInto}, }, - utils::{generate_id, OptionExt, ValueExt}, + utils::{self as helper_utils, generate_id, OptionExt, ValueExt}, }; const OUTGOING_WEBHOOK_TIMEOUT_SECS: u64 = 5; @@ -741,7 +741,7 @@ pub async fn create_event_and_trigger_outgoing_webhook(business_profile, outgoing_webhook, &state).await; + trigger_webhook_to_merchant::(business_profile, outgoing_webhook, state).await; if let Err(e) = result { logger::error!(?e); @@ -752,10 +752,10 @@ pub async fn create_event_and_trigger_outgoing_webhook( +pub async fn trigger_webhook_to_merchant( business_profile: diesel_models::business_profile::BusinessProfile, webhook: api::OutgoingWebhook, - state: &'a AppState, + state: AppState, ) -> CustomResult<(), errors::WebhooksFlowError> { let webhook_details_json = business_profile .webhook_details @@ -806,7 +806,7 @@ pub async fn trigger_webhook_to_merchant<'a, W: types::OutgoingWebhookType + 'a> let response = state .api_client - .send_request(state, request, Some(OUTGOING_WEBHOOK_TIMEOUT_SECS), false) + .send_request(&state, request, Some(OUTGOING_WEBHOOK_TIMEOUT_SECS), false) .await; metrics::WEBHOOK_OUTGOING_COUNT.add( diff --git a/crates/router/src/core/webhooks/types.rs b/crates/router/src/core/webhooks/types.rs index 8feb54e15622..fd1babc2065b 100644 --- a/crates/router/src/core/webhooks/types.rs +++ b/crates/router/src/core/webhooks/types.rs @@ -6,7 +6,7 @@ use serde::Serialize; use crate::{core::errors, headers, services::request::Maskable}; pub trait OutgoingWebhookType: - Serialize + From + Sync + Send + std::fmt::Debug + Serialize + From + Sync + Send + std::fmt::Debug + 'static { fn get_outgoing_webhooks_signature( &self, diff --git a/crates/router/src/routes/locker_migration.rs b/crates/router/src/routes/locker_migration.rs index 892dc5941bd6..a3df0c3a229b 100644 --- a/crates/router/src/routes/locker_migration.rs +++ b/crates/router/src/routes/locker_migration.rs @@ -14,7 +14,7 @@ pub async fn rust_locker_migration( ) -> HttpResponse { let flow = Flow::RustLockerMigration; let merchant_id = path.into_inner(); - api::server_wrap( + Box::pin(api::server_wrap( flow, state, &req, @@ -22,6 +22,6 @@ pub async fn rust_locker_migration( |state, _, _| locker_migration::rust_locker_migration(state, &merchant_id), &auth::AdminApiAuth, api_locking::LockAction::NotApplicable, - ) + )) .await } diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 4dbe5fa562d3..108bfed8579e 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -19,7 +19,7 @@ use common_utils::{ request::RequestContent, }; use error_stack::{report, IntoReport, Report, ResultExt}; -use masking::{ExposeOptionInterface, PeekInterface}; +use masking::PeekInterface; use router_env::{instrument, tracing, tracing_actix_web::RequestId, Tag}; use serde::Serialize; use serde_json::json; @@ -475,7 +475,7 @@ pub async fn send_request( request: Request, option_timeout_secs: Option, ) -> CustomResult { - logger::debug!(method=?request.method, headers=?request.headers, payload=?request.payload, ?request); + logger::debug!(method=?request.method, headers=?request.headers, payload=?request.body, ?request); let url = reqwest::Url::parse(&request.url) .into_report() @@ -501,59 +501,90 @@ pub async fn send_request( value: url.host_str().unwrap_or_default().to_string().into(), }; - let send_request = async { - match request.method { - Method::Get => client.get(url), - Method::Post => { - let client = client.post(url); - match request.body { - Some(RequestContent::Json(payload)) => client.json(&payload), - Some(RequestContent::FormData(form)) => client.multipart(form), - Some(RequestContent::FormUrlEncoded(payload)) => client.form(&payload), - Some(RequestContent::Xml(payload)) => { - let body = quick_xml::se::to_string(&payload).into_report().change_context(errors::ApiClientError::BodySerializationFailed)?; - client.body(body) - .header("Content-Type", "application/xml") - }, - None => client, + let send_request = + async { + match request.method { + Method::Get => client.get(url), + Method::Post => { + let client = client.post(url); + match request.body { + Some(RequestContent::Json(payload)) => client.json( + &payload + .raw_serialize() + .into_report() + .change_context(errors::ApiClientError::BodySerializationFailed)?, + ), + Some(RequestContent::FormData(form)) => client.multipart(form), + Some(RequestContent::FormUrlEncoded(payload)) => client.form( + &payload + .raw_serialize() + .into_report() + .change_context(errors::ApiClientError::BodySerializationFailed)?, + ), + Some(RequestContent::Xml(payload)) => { + let body = quick_xml::se::to_string( + &payload.raw_serialize().into_report().change_context( + errors::ApiClientError::BodySerializationFailed, + )?, + ) + .into_report() + .change_context(errors::ApiClientError::BodySerializationFailed)?; + client.body(body).header("Content-Type", "application/xml") + } + None => client, + } } - } - Method::Put => { - let client = client.put(url); - match request.body { - Some(RequestContent::Json(payload)) => client.json(&payload), - Some(RequestContent::FormData(form)) => client.multipart(form), - Some(RequestContent::FormUrlEncoded(payload)) => client.form(&payload), - Some(RequestContent::Xml(payload)) => { - let body = quick_xml::se::to_string(&payload).into_report().change_context(errors::ApiClientError::BodySerializationFailed)?; - client.body(body) - .header("Content-Type", "application/xml") - }, - None => client, + Method::Put => { + let client = client.put(url); + match request.body { + Some(RequestContent::Json(payload)) => client.json( + &payload + .raw_serialize() + .into_report() + .change_context(errors::ApiClientError::BodySerializationFailed)?, + ), + Some(RequestContent::FormData(form)) => client.multipart(form), + Some(RequestContent::FormUrlEncoded(payload)) => client.form( + &payload + .raw_serialize() + .into_report() + .change_context(errors::ApiClientError::BodySerializationFailed)?, + ), + Some(RequestContent::Xml(payload)) => { + let body = quick_xml::se::to_string( + &payload.raw_serialize().into_report().change_context( + errors::ApiClientError::BodySerializationFailed, + )?, + ) + .into_report() + .change_context(errors::ApiClientError::BodySerializationFailed)?; + client.body(body).header("Content-Type", "application/xml") + } + None => client, + } } - }, - Method::Delete => client.delete(url), - } - .add_headers(headers) - .timeout(Duration::from_secs( - option_timeout_secs.unwrap_or(crate::consts::REQUEST_TIME_OUT), - )) - .send() - .await - .map_err(|error| match error { - error if error.is_timeout() => { - metrics::REQUEST_BUILD_FAILURE.add(&metrics::CONTEXT, 1, &[]); - errors::ApiClientError::RequestTimeoutReceived + Method::Delete => client.delete(url), } - error if is_connection_closed(&error) => { - metrics::REQUEST_BUILD_FAILURE.add(&metrics::CONTEXT, 1, &[]); - errors::ApiClientError::ConnectionClosed - } - _ => errors::ApiClientError::RequestNotSent(error.to_string()), - }) - .into_report() - .attach_printable("Unable to send request to connector") - }; + .add_headers(headers) + .timeout(Duration::from_secs( + option_timeout_secs.unwrap_or(crate::consts::REQUEST_TIME_OUT), + )) + .send() + .await + .map_err(|error| match error { + error if error.is_timeout() => { + metrics::REQUEST_BUILD_FAILURE.add(&metrics::CONTEXT, 1, &[]); + errors::ApiClientError::RequestTimeoutReceived + } + error if is_connection_closed(&error) => { + metrics::REQUEST_BUILD_FAILURE.add(&metrics::CONTEXT, 1, &[]); + errors::ApiClientError::ConnectionClosed + } + _ => errors::ApiClientError::RequestNotSent(error.to_string()), + }) + .into_report() + .attach_printable("Unable to send request to connector") + }; metrics_request::record_operation_time( send_request, From 06590e8eda538feef05cd18d43376099f7dccfae Mon Sep 17 00:00:00 2001 From: Prasunna Soppa Date: Tue, 28 Nov 2023 15:19:14 +0530 Subject: [PATCH 05/19] feat(events): update request type for outgoing calls for connectors --- crates/router/src/connector/aci.rs | 6 +++--- crates/router/src/connector/boku.rs | 8 ++++---- crates/router/src/connector/cryptopay.rs | 6 +++++- crates/router/src/connector/iatapay.rs | 2 +- crates/router/src/connector/nmi.rs | 14 +++++++------- crates/router/src/connector/paypal.rs | 2 +- crates/router/src/connector/payu.rs | 2 +- crates/router/src/connector/shift4.rs | 2 +- crates/router/src/connector/stripe.rs | 2 +- crates/router/src/connector/trustpay.rs | 4 ++-- crates/router/src/connector/volt.rs | 2 +- crates/router/src/services/api.rs | 9 +++++++++ 12 files changed, 36 insertions(+), 23 deletions(-) diff --git a/crates/router/src/connector/aci.rs b/crates/router/src/connector/aci.rs index 7e82da66c813..b35594164189 100644 --- a/crates/router/src/connector/aci.rs +++ b/crates/router/src/connector/aci.rs @@ -294,7 +294,7 @@ impl ))?; let connector_req = aci::AciPaymentsRequest::try_from(&connector_router_data)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -390,7 +390,7 @@ impl _connectors: &settings::Connectors, ) -> CustomResult { let connector_req = aci::AciCancelRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( &self, @@ -487,7 +487,7 @@ impl services::ConnectorIntegration CustomResult { let connector_req = boku::BokuPaymentsRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::Xml(Box::new(connector_req))) } fn build_request( @@ -297,7 +297,7 @@ impl ConnectorIntegration CustomResult { let connector_req = boku::BokuPsyncRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::Xml(Box::new(connector_req))) } fn build_request( @@ -464,7 +464,7 @@ impl ConnectorIntegration CustomResult { let connector_req = boku::BokuRefundRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::Xml(Box::new(connector_req))) } fn build_request( @@ -542,7 +542,7 @@ impl ConnectorIntegration CustomResult { let connector_req = boku::BokuRsyncRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::Xml(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/cryptopay.rs b/crates/router/src/connector/cryptopay.rs index 5e4070658253..3ebc4b7cb798 100644 --- a/crates/router/src/connector/cryptopay.rs +++ b/crates/router/src/connector/cryptopay.rs @@ -69,7 +69,7 @@ where req: &types::RouterData, connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { - let api_method = "POST".to_string(); + let api_method = self.get_http_method().to_string(); let body = types::RequestBody::get_inner_value(self.get_request_body(req, connectors)?) .peek() .to_owned(); @@ -294,6 +294,10 @@ impl ConnectorIntegration services::Method { + services::Method::Get + } + fn get_url( &self, req: &types::PaymentsSyncRouterData, diff --git a/crates/router/src/connector/iatapay.rs b/crates/router/src/connector/iatapay.rs index a6316649b2e3..0871d3c38984 100644 --- a/crates/router/src/connector/iatapay.rs +++ b/crates/router/src/connector/iatapay.rs @@ -181,7 +181,7 @@ impl ConnectorIntegration CustomResult { let connector_req = iatapay::IatapayAuthUpdateRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/nmi.rs b/crates/router/src/connector/nmi.rs index f3c8af5f6058..83a62b130104 100644 --- a/crates/router/src/connector/nmi.rs +++ b/crates/router/src/connector/nmi.rs @@ -144,7 +144,7 @@ impl _connectors: &settings::Connectors, ) -> CustomResult { let connector_req = nmi::NmiPaymentsRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -218,7 +218,7 @@ impl ConnectorIntegration CustomResult { let connector_req = nmi::NmiSyncRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -361,7 +361,7 @@ impl ConnectorIntegration CustomResult { let connector_req = nmi::NmiCancelRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -503,7 +503,7 @@ impl ConnectorIntegration CustomResult { let connector_req = nmi::NmiSyncRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/paypal.rs b/crates/router/src/connector/paypal.rs index 2a8a45181472..34a2029c5729 100644 --- a/crates/router/src/connector/paypal.rs +++ b/crates/router/src/connector/paypal.rs @@ -321,7 +321,7 @@ impl ConnectorIntegration CustomResult { let connector_req = paypal::PaypalAuthUpdateRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/payu.rs b/crates/router/src/connector/payu.rs index 4ea69b6a7fc0..56b71e11a92f 100644 --- a/crates/router/src/connector/payu.rs +++ b/crates/router/src/connector/payu.rs @@ -249,7 +249,7 @@ impl ConnectorIntegration CustomResult { let connector_req = payu::PayuAuthUpdateRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/shift4.rs b/crates/router/src/connector/shift4.rs index b2ea8110dc2e..3039fad44b21 100644 --- a/crates/router/src/connector/shift4.rs +++ b/crates/router/src/connector/shift4.rs @@ -475,7 +475,7 @@ impl _connectors: &settings::Connectors, ) -> CustomResult { let connector_req = shift4::Shift4PaymentsRequest::try_from(req)?; - Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index 681ecb64d06f..8e5de2034f26 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -149,7 +149,7 @@ impl ) -> CustomResult { let connector_req = stripe::StripeCreditTransferSourceRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/trustpay.rs b/crates/router/src/connector/trustpay.rs index 47ff0794fda6..739ee5342a40 100644 --- a/crates/router/src/connector/trustpay.rs +++ b/crates/router/src/connector/trustpay.rs @@ -241,7 +241,7 @@ impl ConnectorIntegration CustomResult { let connector_req = trustpay::TrustpayAuthUpdateRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( @@ -453,7 +453,7 @@ impl ))?; let connector_req = trustpay::TrustpayCreateIntentRequest::try_from(&connector_router_data)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/connector/volt.rs b/crates/router/src/connector/volt.rs index 581d978165ba..75adf0c1c8b4 100644 --- a/crates/router/src/connector/volt.rs +++ b/crates/router/src/connector/volt.rs @@ -179,7 +179,7 @@ impl ConnectorIntegration CustomResult { let connector_req = volt::VoltAuthUpdateRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 108bfed8579e..bb9089c9aeda 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -278,6 +278,15 @@ where { // If needed add an error stack as follows // connector_integration.build_request(req).attach_printable("Failed to build request"); + logger::debug!(connector_request=?connector_request); + let maked_conn_req = connector_request.as_ref().map(|req| match &req.body { + Some(RequestContent::Json(payload)) + | Some(RequestContent::FormUrlEncoded(payload)) + | Some(RequestContent::Xml(payload)) => payload.masked_serialize().unwrap_or_default(), + _ => serde_json::Value::Null, + }); + logger::debug!(connector_request_body=?maked_conn_req); + logger::debug!(payment_id=?req.payment_id); let mut router_data = req.clone(); match call_connector_action { payments::CallConnectorAction::HandleResponse(res) => { From df6d4f9145cfc2f8d2a1d9acfedee878bd740a22 Mon Sep 17 00:00:00 2001 From: Sampras lopes Date: Sun, 3 Dec 2023 00:46:40 +0530 Subject: [PATCH 06/19] fix(connector): fix serialization failures for non-json formats with erasedmaskserialize --- Cargo.lock | 1 + crates/common_utils/src/request.rs | 7 +- crates/masking/Cargo.toml | 1 + crates/masking/src/serde.rs | 25 ++++-- crates/router/src/core/webhooks.rs | 4 +- crates/router/src/services/api.rs | 121 +++++++++++------------------ 6 files changed, 69 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 376254bc3279..d99f3b02f0ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3568,6 +3568,7 @@ version = "0.1.0" dependencies = [ "bytes 1.5.0", "diesel", + "erased-serde", "serde", "serde_json", "subtle", diff --git a/crates/common_utils/src/request.rs b/crates/common_utils/src/request.rs index e11a4dec5e40..ea96f38d04c6 100644 --- a/crates/common_utils/src/request.rs +++ b/crates/common_utils/src/request.rs @@ -2,7 +2,6 @@ use masking::{Maskable, Secret}; #[cfg(feature = "logs")] use router_env::logger; use serde::{Deserialize, Serialize}; -use serde_json::json; use crate::errors; @@ -197,11 +196,7 @@ impl RequestBody { match request_body { RequestContent::Json(i) | RequestContent::FormUrlEncoded(i) - | RequestContent::Xml(i) => { - serde_json::to_string(&i.raw_serialize().unwrap_or(json!(""))) - .unwrap_or_default() - .into() - } + | RequestContent::Xml(i) => serde_json::to_string(&i).unwrap_or_default().into(), RequestContent::FormData(_) => String::new().into(), } } diff --git a/crates/masking/Cargo.toml b/crates/masking/Cargo.toml index bf92e867dc6c..2c54cd4da4b6 100644 --- a/crates/masking/Cargo.toml +++ b/crates/masking/Cargo.toml @@ -19,6 +19,7 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] bytes = { version = "1", optional = true } diesel = { version = "2.1.0", features = ["postgres", "serde_json", "time"], optional = true } +erased-serde = "0.3.31" serde = { version = "1", features = ["derive"], optional = true } serde_json = { version = "1.0.96", optional = true } subtle = "=2.4.1" diff --git a/crates/masking/src/serde.rs b/crates/masking/src/serde.rs index d1845ee29033..944392e693ff 100644 --- a/crates/masking/src/serde.rs +++ b/crates/masking/src/serde.rs @@ -2,6 +2,7 @@ //! Serde-related. //! +pub use erased_serde::Serialize as ErasedSerialize; pub use serde::{de, ser, Deserialize, Serialize, Serializer}; use serde_json::{value::Serializer as JsonValueSerializer, Value}; @@ -99,20 +100,32 @@ pub fn masked_serialize(value: &T) -> Result because of Rust's "object safety" rules. /// In particular, the trait contains generic methods which cannot be made into a trait object. /// In this case we remove the generic for assuming the serialization to be of 2 types only raw json or masked json -pub trait ErasedMaskSerialize { +pub trait ErasedMaskSerialize: ErasedSerialize { /// Masked serialization. fn masked_serialize(&self) -> Result; - /// Normal serialization. - fn raw_serialize(&self) -> Result; } -impl ErasedMaskSerialize for T { +impl ErasedMaskSerialize for T { fn masked_serialize(&self) -> Result { masked_serialize(self) } +} + +impl<'a> Serialize for dyn ErasedMaskSerialize + 'a { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + erased_serde::serialize(self, serializer) + } +} - fn raw_serialize(&self) -> Result { - serde_json::to_value(self) +impl<'a> Serialize for dyn ErasedMaskSerialize + 'a + Send { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + erased_serde::serialize(self, serializer) } } diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index 5a70a1036f4f..d58e0ab86582 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -1133,9 +1133,7 @@ pub async fn webhooks_core client.get(url), - Method::Post => { - let client = client.post(url); - match request.body { - Some(RequestContent::Json(payload)) => client.json( - &payload - .raw_serialize() - .into_report() - .change_context(errors::ApiClientError::BodySerializationFailed)?, - ), - Some(RequestContent::FormData(form)) => client.multipart(form), - Some(RequestContent::FormUrlEncoded(payload)) => client.form( - &payload - .raw_serialize() - .into_report() - .change_context(errors::ApiClientError::BodySerializationFailed)?, - ), - Some(RequestContent::Xml(payload)) => { - let body = quick_xml::se::to_string( - &payload.raw_serialize().into_report().change_context( - errors::ApiClientError::BodySerializationFailed, - )?, - ) + let send_request = async { + match request.method { + Method::Get => client.get(url), + Method::Post => { + let client = client.post(url); + match request.body { + Some(RequestContent::Json(payload)) => client.json(&payload), + Some(RequestContent::FormData(form)) => client.multipart(form), + Some(RequestContent::FormUrlEncoded(payload)) => client.form(&payload), + Some(RequestContent::Xml(payload)) => { + let body = quick_xml::se::to_string(&payload) .into_report() .change_context(errors::ApiClientError::BodySerializationFailed)?; - client.body(body).header("Content-Type", "application/xml") - } - None => client, + client.body(body).header("Content-Type", "application/xml") } + None => client, } - Method::Put => { - let client = client.put(url); - match request.body { - Some(RequestContent::Json(payload)) => client.json( - &payload - .raw_serialize() - .into_report() - .change_context(errors::ApiClientError::BodySerializationFailed)?, - ), - Some(RequestContent::FormData(form)) => client.multipart(form), - Some(RequestContent::FormUrlEncoded(payload)) => client.form( - &payload - .raw_serialize() - .into_report() - .change_context(errors::ApiClientError::BodySerializationFailed)?, - ), - Some(RequestContent::Xml(payload)) => { - let body = quick_xml::se::to_string( - &payload.raw_serialize().into_report().change_context( - errors::ApiClientError::BodySerializationFailed, - )?, - ) + } + Method::Put => { + let client = client.put(url); + match request.body { + Some(RequestContent::Json(payload)) => client.json(&payload), + Some(RequestContent::FormData(form)) => client.multipart(form), + Some(RequestContent::FormUrlEncoded(payload)) => client.form(&payload), + Some(RequestContent::Xml(payload)) => { + let body = quick_xml::se::to_string(&payload) .into_report() .change_context(errors::ApiClientError::BodySerializationFailed)?; - client.body(body).header("Content-Type", "application/xml") - } - None => client, + client.body(body).header("Content-Type", "application/xml") } + None => client, } - Method::Delete => client.delete(url), } - .add_headers(headers) - .timeout(Duration::from_secs( - option_timeout_secs.unwrap_or(crate::consts::REQUEST_TIME_OUT), - )) - .send() - .await - .map_err(|error| match error { - error if error.is_timeout() => { - metrics::REQUEST_BUILD_FAILURE.add(&metrics::CONTEXT, 1, &[]); - errors::ApiClientError::RequestTimeoutReceived - } - error if is_connection_closed(&error) => { - metrics::REQUEST_BUILD_FAILURE.add(&metrics::CONTEXT, 1, &[]); - errors::ApiClientError::ConnectionClosed - } - _ => errors::ApiClientError::RequestNotSent(error.to_string()), - }) - .into_report() - .attach_printable("Unable to send request to connector") - }; + Method::Delete => client.delete(url), + } + .add_headers(headers) + .timeout(Duration::from_secs( + option_timeout_secs.unwrap_or(crate::consts::REQUEST_TIME_OUT), + )) + .send() + .await + .map_err(|error| match error { + error if error.is_timeout() => { + metrics::REQUEST_BUILD_FAILURE.add(&metrics::CONTEXT, 1, &[]); + errors::ApiClientError::RequestTimeoutReceived + } + error if is_connection_closed(&error) => { + metrics::REQUEST_BUILD_FAILURE.add(&metrics::CONTEXT, 1, &[]); + errors::ApiClientError::ConnectionClosed + } + _ => errors::ApiClientError::RequestNotSent(error.to_string()), + }) + .into_report() + .attach_printable("Unable to send request to connector") + }; metrics_request::record_operation_time( send_request, From 2bf71198d9c0c90d601d98e3801c6f11d3acbc30 Mon Sep 17 00:00:00 2001 From: Sampras lopes Date: Sun, 3 Dec 2023 03:39:24 +0530 Subject: [PATCH 07/19] feat(events): add event generation for connector api calls --- config/config.example.toml | 18 +++++ config/docker_compose.toml | 2 +- crates/router/src/core/refunds.rs | 4 +- crates/router/src/events.rs | 2 + .../router/src/events/connector_api_logs.rs | 65 +++++++++++++++++++ crates/router/src/routes/app.rs | 5 +- crates/router/src/services/api.rs | 43 +++++++++++- crates/router/src/services/kafka.rs | 14 +++- 8 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 crates/router/src/events/connector_api_logs.rs diff --git a/config/config.example.toml b/config/config.example.toml index d935a4e7f20d..14318e9200e7 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -464,6 +464,12 @@ sdk_url = "http://localhost:9090/dist/HyperLoader.js" [analytics] source = "sqlx" # The Analytics source/strategy to be used +[analytics.clickhouse] +username = "" # Clickhouse username +password = "" # Clickhouse password (optional) +host = "" # Clickhouse host in http(s)://: format +database_name = "" # Clickhouse database name + [analytics.sqlx] username = "db_user" # Analytics DB Username password = "db_pass" # Analytics DB Password @@ -472,8 +478,20 @@ port = 5432 # Analytics DB Port dbname = "hyperswitch_db" # Name of Database pool_size = 5 # Number of connections to keep open connection_timeout = 10 # Timeout for database connection in seconds +queue_strategy = "Fifo" # Add the queue strategy used by the database bb8 client # Config for KV setup [kv_config] # TTL for KV in seconds ttl = 900 + +[events] +source = "logs" # The event sink to push events supports kafka or logs (stdout) + +[events.kafka] +brokers = [] # Kafka broker urls for bootstrapping the client +intent_analytics_topic = "topic" # Kafka topic to be used for PaymentIntent events +attempt_analytics_topic = "topic" # Kafka topic to be used for PaymentAttempt events +refund_analytics_topic = "topic" # Kafka topic to be used for Refund events +api_logs_topic = "topic" # Kafka topic to be used for incoming api events +connector_logs_topic = "topic" # Kafka topic to be used for connector api events \ No newline at end of file diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 4d50600e1bf8..6bc6fcd69ea6 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -339,7 +339,7 @@ intent_analytics_topic = "hyperswitch-payment-intent-events" attempt_analytics_topic = "hyperswitch-payment-attempt-events" refund_analytics_topic = "hyperswitch-refund-events" api_logs_topic = "hyperswitch-api-log-events" -connector_events_topic = "hyperswitch-connector-api-events" +connector_logs_topic = "hyperswitch-connector-api-events" [analytics] source = "sqlx" diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index c43c00b7259c..3d150e6eb4c8 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -929,7 +929,9 @@ pub async fn start_refund_workflow( refund_tracker: &storage::ProcessTracker, ) -> Result<(), errors::ProcessTrackerError> { match refund_tracker.name.as_deref() { - Some("EXECUTE_REFUND") => trigger_refund_execute_workflow(state, refund_tracker).await, + Some("EXECUTE_REFUND") => { + Box::pin(trigger_refund_execute_workflow(state, refund_tracker)).await + } Some("SYNC_REFUND") => { Box::pin(sync_refund_with_gateway_workflow(state, refund_tracker)).await } diff --git a/crates/router/src/events.rs b/crates/router/src/events.rs index 8f980fee504a..2dc9258e19df 100644 --- a/crates/router/src/events.rs +++ b/crates/router/src/events.rs @@ -6,6 +6,7 @@ use storage_impl::errors::ApplicationError; use crate::{db::KafkaProducer, services::kafka::KafkaSettings}; pub mod api_logs; +pub mod connector_api_logs; pub mod event_logger; pub mod kafka_handler; @@ -29,6 +30,7 @@ pub enum EventType { PaymentAttempt, Refund, ApiLogs, + ConnectorApiLogs, } #[derive(Debug, Default, Deserialize, Clone)] diff --git a/crates/router/src/events/connector_api_logs.rs b/crates/router/src/events/connector_api_logs.rs new file mode 100644 index 000000000000..38f972089ad9 --- /dev/null +++ b/crates/router/src/events/connector_api_logs.rs @@ -0,0 +1,65 @@ +use common_utils::request::Method; +use router_env::tracing_actix_web::RequestId; +use serde::Serialize; +use time::OffsetDateTime; + +use super::{EventType, RawEvent}; + +#[derive(Debug, Serialize)] +pub struct ConnectorEvent { + connector_name: String, + flow: String, + request: String, + response: Option, + url: String, + method: String, + payment_id: String, + merchant_id: String, + created_at: i128, + request_id: String, + latency: u128, +} + +impl ConnectorEvent { + #[allow(clippy::too_many_arguments)] + pub fn new( + connector_name: String, + flow: String, + request: serde_json::Value, + response: Option, + url: String, + method: Method, + payment_id: String, + merchant_id: String, + request_id: &Option, + latency: u128, + ) -> Self { + Self { + connector_name, + flow, + request: request.to_string(), + response, + url, + method: method.to_string(), + payment_id, + merchant_id, + created_at: OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000, + request_id: request_id + .map(|i| i.as_hyphenated().to_string()) + .unwrap_or("NO_REQUEST_ID".to_string()), + latency, + } + } +} + +impl TryFrom for RawEvent { + type Error = serde_json::Error; + + fn try_from(value: ConnectorEvent) -> Result { + Ok(Self { + event_type: EventType::ConnectorApiLogs, + key: value.request_id.clone(), + payload: serde_json::to_value(value)?, + }) + } +} diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index a145f3e7e5d7..8ba1c3b02c14 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -57,6 +57,7 @@ pub struct AppState { pub api_client: Box, #[cfg(feature = "olap")] pub pool: crate::analytics::AnalyticsProvider, + pub request_id: Option, } impl scheduler::SchedulerAppState for AppState { @@ -93,7 +94,8 @@ impl AppStateInfo for AppState { } fn add_request_id(&mut self, request_id: RequestId) { self.api_client.add_request_id(request_id); - self.store.add_request_id(request_id.to_string()) + self.store.add_request_id(request_id.to_string()); + self.request_id.replace(request_id); } fn add_merchant_id(&mut self, merchant_id: Option) { @@ -212,6 +214,7 @@ impl AppState { event_handler, #[cfg(feature = "olap")] pool, + request_id: None, } }) .await diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 3f49e666c2e1..e3a3e82f72ef 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -35,7 +35,10 @@ use crate::{ errors::{self, CustomResult}, payments, }, - events::api_logs::{ApiEvent, ApiEventMetric, ApiEventsType}, + events::{ + api_logs::{ApiEvent, ApiEventMetric, ApiEventsType}, + connector_api_logs::ConnectorEvent, + }, logger, routes::{ app::AppStateInfo, @@ -361,10 +364,48 @@ where match connector_request { Some(request) => { logger::debug!(connector_request=?request); + + let masked_request_body = match &request.body { + Some(request) => match request { + RequestContent::Json(i) + | RequestContent::FormUrlEncoded(i) + | RequestContent::Xml(i) => i + .masked_serialize() + .unwrap_or(json!({ "error": "failed to mask serialize"})), + RequestContent::FormData(_) => json!({"request_type": "FORM_DATA"}), + }, + None => json!({"error": "EMPTY_REQUEST_BODY"}), + }; + let request_url = request.url.clone(); + let request_method = request.method; + let current_time = Instant::now(); let response = call_connector_api(state, request).await; let external_latency = current_time.elapsed().as_millis(); logger::debug!(connector_response=?response); + + let connector_event = ConnectorEvent::new( + req.connector.clone(), + std::any::type_name::().to_string(), + masked_request_body, + None, + request_url, + request_method, + req.payment_id.clone(), + req.merchant_id.clone(), + &state.request_id, + external_latency, + ); + + match connector_event.try_into() { + Ok(event) => { + state.event_handler().log_event(event); + } + Err(err) => { + logger::error!(error=?err, "Error Logging Connector Event"); + } + } + match response { Ok(body) => { let response = match body { diff --git a/crates/router/src/services/kafka.rs b/crates/router/src/services/kafka.rs index 497ac16721b5..4c65b4677872 100644 --- a/crates/router/src/services/kafka.rs +++ b/crates/router/src/services/kafka.rs @@ -83,6 +83,7 @@ pub struct KafkaSettings { attempt_analytics_topic: String, refund_analytics_topic: String, api_logs_topic: String, + connector_logs_topic: String, } impl KafkaSettings { @@ -119,7 +120,15 @@ impl KafkaSettings { Err(ApplicationError::InvalidConfigurationValueError( "Kafka API event Analytics topic must not be empty".into(), )) - }) + })?; + + common_utils::fp_utils::when(self.connector_logs_topic.is_default_or_empty(), || { + Err(ApplicationError::InvalidConfigurationValueError( + "Kafka Connector Logs topic must not be empty".into(), + )) + })?; + + Ok(()) } } @@ -130,6 +139,7 @@ pub struct KafkaProducer { attempt_analytics_topic: String, refund_analytics_topic: String, api_logs_topic: String, + connector_logs_topic: String, } struct RdKafkaProducer(ThreadedProducer); @@ -166,6 +176,7 @@ impl KafkaProducer { attempt_analytics_topic: conf.attempt_analytics_topic.clone(), refund_analytics_topic: conf.refund_analytics_topic.clone(), api_logs_topic: conf.api_logs_topic.clone(), + connector_logs_topic: conf.connector_logs_topic.clone(), }) } @@ -297,6 +308,7 @@ impl KafkaProducer { EventType::PaymentAttempt => &self.attempt_analytics_topic, EventType::PaymentIntent => &self.intent_analytics_topic, EventType::Refund => &self.refund_analytics_topic, + EventType::ConnectorApiLogs => &self.connector_logs_topic, } } } From fbe5317ef485c65798472f2067c9142ac908b9f0 Mon Sep 17 00:00:00 2001 From: Sampras lopes Date: Sun, 3 Dec 2023 03:50:17 +0530 Subject: [PATCH 08/19] chore(lints): fix spell check lints --- crates/router/src/services/api.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index e3a3e82f72ef..8734b878f062 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -283,13 +283,13 @@ where // If needed add an error stack as follows // connector_integration.build_request(req).attach_printable("Failed to build request"); logger::debug!(connector_request=?connector_request); - let maked_conn_req = connector_request.as_ref().map(|req| match &req.body { + let masked_conn_req = connector_request.as_ref().map(|req| match &req.body { Some(RequestContent::Json(payload)) | Some(RequestContent::FormUrlEncoded(payload)) | Some(RequestContent::Xml(payload)) => payload.masked_serialize().unwrap_or_default(), _ => serde_json::Value::Null, }); - logger::debug!(connector_request_body=?maked_conn_req); + logger::debug!(connector_request_body=?masked_conn_req); logger::debug!(payment_id=?req.payment_id); let mut router_data = req.clone(); match call_connector_action { From 8d6b967e6b52367790a8662375b18f3682a96672 Mon Sep 17 00:00:00 2001 From: Sampras lopes Date: Sun, 3 Dec 2023 12:58:03 +0530 Subject: [PATCH 09/19] chore(lints): fix clippy lints --- crates/router/src/connector/wise.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/router/src/connector/wise.rs b/crates/router/src/connector/wise.rs index e8e772bb9f3f..d41b44aaec5e 100644 --- a/crates/router/src/connector/wise.rs +++ b/crates/router/src/connector/wise.rs @@ -1,6 +1,7 @@ pub mod transformers; use std::fmt::Debug; +#[cfg(feature = "payouts")] use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; #[cfg(feature = "payouts")] From d291426fa6e83d3c3d758cf03856ec97fd2ab698 Mon Sep 17 00:00:00 2001 From: Sampras lopes Date: Fri, 8 Dec 2023 14:09:09 +0530 Subject: [PATCH 10/19] chore(deps): merge origin/main branch --- .github/CODEOWNERS | 54 ++ .github/workflows/CI-pr.yml | 20 + CHANGELOG.md | 84 ++ Cargo.lock | 25 + config/config.example.toml | 27 +- config/development.toml | 20 +- config/docker_compose.toml | 21 +- crates/api_models/Cargo.toml | 5 +- crates/api_models/src/admin.rs | 2 +- crates/api_models/src/connector_onboarding.rs | 54 ++ crates/api_models/src/enums.rs | 55 ++ crates/api_models/src/events.rs | 1 + .../src/events/connector_onboarding.rs | 12 + crates/api_models/src/events/payment.rs | 13 +- crates/api_models/src/events/user.rs | 21 +- crates/api_models/src/lib.rs | 2 + crates/api_models/src/payment_methods.rs | 181 +--- crates/api_models/src/payments.rs | 101 ++- crates/api_models/src/pm_auth.rs | 57 ++ crates/api_models/src/refunds.rs | 2 +- .../src/surcharge_decision_configs.rs | 11 +- crates/api_models/src/user.rs | 70 +- .../api_models/src/user/dashboard_metadata.rs | 41 +- crates/common_enums/src/enums.rs | 27 + crates/common_utils/src/events.rs | 1 + crates/common_utils/src/lib.rs | 1 + crates/common_utils/src/macros.rs | 92 +++ crates/common_utils/src/request.rs | 1 + crates/common_utils/src/types.rs | 15 +- crates/data_models/Cargo.toml | 1 + crates/data_models/src/errors.rs | 4 +- crates/data_models/src/payments.rs | 1 + .../src/payments/payment_attempt.rs | 4 + .../src/payments/payment_intent.rs | 18 + crates/diesel_models/src/authorization.rs | 78 ++ crates/diesel_models/src/enums.rs | 3 + crates/diesel_models/src/errors.rs | 16 +- crates/diesel_models/src/kv.rs | 3 + crates/diesel_models/src/lib.rs | 1 + crates/diesel_models/src/payment_attempt.rs | 12 + crates/diesel_models/src/payment_intent.rs | 22 +- crates/diesel_models/src/query.rs | 1 + .../diesel_models/src/query/authorization.rs | 79 ++ .../src/query/dashboard_metadata.rs | 45 +- crates/diesel_models/src/schema.rs | 27 + crates/drainer/src/lib.rs | 14 +- crates/drainer/src/metrics.rs | 6 +- crates/drainer/src/utils.rs | 19 + crates/pm_auth/Cargo.toml | 27 + crates/pm_auth/README.md | 3 + crates/pm_auth/src/connector.rs | 3 + crates/pm_auth/src/connector/plaid.rs | 340 ++++++++ .../src/connector/plaid/transformers.rs | 294 +++++++ crates/pm_auth/src/consts.rs | 5 + crates/pm_auth/src/core.rs | 1 + crates/pm_auth/src/core/errors.rs | 27 + crates/pm_auth/src/lib.rs | 4 + crates/pm_auth/src/types.rs | 152 ++++ crates/pm_auth/src/types/api.rs | 167 ++++ crates/pm_auth/src/types/api/auth_service.rs | 40 + crates/router/Cargo.toml | 4 +- crates/router/src/configs/defaults.rs | 237 +++++- crates/router/src/configs/kms.rs | 33 + crates/router/src/configs/settings.rs | 34 + crates/router/src/connector.rs | 7 +- .../connector/bankofamerica/transformers.rs | 82 +- .../src/connector/braintree/transformers.rs | 7 +- .../src/connector/checkout/transformers.rs | 24 +- crates/router/src/connector/cybersource.rs | 110 ++- .../src/connector/cybersource/transformers.rs | 152 +++- crates/router/src/connector/signifyd.rs | 624 ++++++++++++++ .../src/connector/signifyd/transformers.rs | 7 + .../connector/signifyd/transformers/api.rs | 589 ++++++++++++++ .../connector/signifyd/transformers/auth.rs | 20 + .../src/connector/stripe/transformers.rs | 26 +- crates/router/src/connector/trustpay.rs | 20 +- .../src/connector/trustpay/transformers.rs | 27 +- crates/router/src/connector/utils.rs | 79 +- crates/router/src/core.rs | 5 + crates/router/src/core/admin.rs | 157 +++- .../router/src/core/connector_onboarding.rs | 96 +++ .../src/core/connector_onboarding/paypal.rs | 174 ++++ crates/router/src/core/errors/user.rs | 29 + .../src/core/errors/user/sample_data.rs | 24 +- crates/router/src/core/errors/utils.rs | 22 + crates/router/src/core/fraud_check.rs | 770 ++++++++++++++++++ crates/router/src/core/fraud_check/flows.rs | 36 + .../core/fraud_check/flows/checkout_flow.rs | 147 ++++ .../fraud_check/flows/fulfillment_flow.rs | 110 +++ .../core/fraud_check/flows/record_return.rs | 149 ++++ .../src/core/fraud_check/flows/sale_flow.rs | 145 ++++ .../fraud_check/flows/transaction_flow.rs | 158 ++++ .../router/src/core/fraud_check/operation.rs | 106 +++ .../fraud_check/operation/fraud_check_post.rs | 457 +++++++++++ .../fraud_check/operation/fraud_check_pre.rs | 337 ++++++++ crates/router/src/core/fraud_check/types.rs | 208 +++++ crates/router/src/core/payment_methods.rs | 23 +- .../router/src/core/payment_methods/cards.rs | 297 ++++++- .../surcharge_decision_configs.rs | 184 +++-- crates/router/src/core/payments.rs | 470 ++++++----- crates/router/src/core/payments/flows.rs | 576 +++++++++++++ .../flows/incremental_authorization_flow.rs | 118 +++ crates/router/src/core/payments/helpers.rs | 132 ++- crates/router/src/core/payments/operations.rs | 3 +- .../payments/operations/payment_approve.rs | 8 +- .../payments/operations/payment_cancel.rs | 2 + .../payments/operations/payment_capture.rs | 2 + .../operations/payment_complete_authorize.rs | 12 +- .../payments/operations/payment_confirm.rs | 97 +-- .../payments/operations/payment_create.rs | 92 ++- .../payments/operations/payment_reject.rs | 2 + .../payments/operations/payment_response.rs | 139 +++- .../payments/operations/payment_session.rs | 2 + .../core/payments/operations/payment_start.rs | 2 + .../payments/operations/payment_status.rs | 16 + .../payments/operations/payment_update.rs | 22 +- .../payments_incremental_authorization.rs | 328 ++++++++ .../router/src/core/payments/transformers.rs | 62 +- crates/router/src/core/payments/types.rs | 205 ++++- crates/router/src/core/pm_auth.rs | 729 +++++++++++++++++ crates/router/src/core/pm_auth/helpers.rs | 33 + .../router/src/core/pm_auth/transformers.rs | 18 + crates/router/src/core/user.rs | 434 ++++++++-- .../src/core/user/dashboard_metadata.rs | 136 +++- crates/router/src/core/utils.rs | 76 +- crates/router/src/db.rs | 2 + crates/router/src/db/authorization.rs | 104 +++ crates/router/src/db/kafka_store.rs | 36 + crates/router/src/db/refund.rs | 157 ++-- crates/router/src/db/reverse_lookup.rs | 3 +- crates/router/src/db/user/sample_data.rs | 8 +- crates/router/src/lib.rs | 2 + crates/router/src/macros.rs | 72 +- crates/router/src/openapi.rs | 8 +- crates/router/src/routes.rs | 12 +- crates/router/src/routes/admin.rs | 4 +- crates/router/src/routes/app.rs | 95 ++- .../router/src/routes/connector_onboarding.rs | 47 ++ crates/router/src/routes/fraud_check.rs | 42 + crates/router/src/routes/lock_utils.rs | 20 +- crates/router/src/routes/metrics.rs | 1 + crates/router/src/routes/payments.rs | 229 ++++++ crates/router/src/routes/pm_auth.rs | 73 ++ crates/router/src/routes/user.rs | 137 +++- crates/router/src/services.rs | 1 + crates/router/src/services/api.rs | 18 + crates/router/src/services/authentication.rs | 12 + crates/router/src/services/email/types.rs | 18 +- crates/router/src/services/pm_auth.rs | 95 +++ crates/router/src/types.rs | 41 +- crates/router/src/types/api.rs | 42 +- .../src/types/api/connector_onboarding.rs | 1 + .../types/api/connector_onboarding/paypal.rs | 247 ++++++ crates/router/src/types/api/fraud_check.rs | 91 +++ crates/router/src/types/api/payments.rs | 24 +- crates/router/src/types/domain/user.rs | 209 ++++- .../types/domain/user/dashboard_metadata.rs | 6 + crates/router/src/types/fraud_check.rs | 126 +++ crates/router/src/types/pm_auth.rs | 38 + crates/router/src/types/storage.rs | 21 +- .../router/src/types/storage/authorization.rs | 1 + .../router/src/types/storage/fraud_check.rs | 3 + crates/router/src/types/storage/refund.rs | 75 +- crates/router/src/types/transformers.rs | 15 +- crates/router/src/utils.rs | 2 + .../router/src/utils/connector_onboarding.rs | 36 + .../src/utils/connector_onboarding/paypal.rs | 78 ++ crates/router/src/utils/db_utils.rs | 8 +- crates/router/src/utils/user.rs | 64 +- .../src/utils/user/dashboard_metadata.rs | 129 ++- crates/router/src/utils/user/sample_data.rs | 3 +- crates/router/tests/connectors/utils.rs | 2 + crates/router_derive/src/macros/operation.rs | 12 +- crates/router_env/src/logger/types.rs | 26 + crates/router_env/src/metrics.rs | 19 + crates/storage_impl/src/errors.rs | 30 +- crates/storage_impl/src/lib.rs | 10 +- crates/storage_impl/src/lookup.rs | 3 +- .../src/mock_db/payment_intent.rs | 1 + .../src/payments/payment_attempt.rs | 142 ++-- .../src/payments/payment_intent.rs | 45 +- crates/storage_impl/src/utils.rs | 5 +- crates/test_utils/README.md | 8 +- loadtest/config/development.toml | 13 +- .../down.sql | 2 + .../up.sql | 16 + .../down.sql | 2 + .../up.sql | 2 + openapi/openapi_spec.json | 252 +++++- .../adyen_uk.postman_collection.json | 4 +- scripts/add_connector.sh | 4 +- 191 files changed, 13601 insertions(+), 1318 deletions(-) create mode 100644 crates/api_models/src/connector_onboarding.rs create mode 100644 crates/api_models/src/events/connector_onboarding.rs create mode 100644 crates/api_models/src/pm_auth.rs create mode 100644 crates/common_utils/src/macros.rs create mode 100644 crates/diesel_models/src/authorization.rs create mode 100644 crates/diesel_models/src/query/authorization.rs create mode 100644 crates/pm_auth/Cargo.toml create mode 100644 crates/pm_auth/README.md create mode 100644 crates/pm_auth/src/connector.rs create mode 100644 crates/pm_auth/src/connector/plaid.rs create mode 100644 crates/pm_auth/src/connector/plaid/transformers.rs create mode 100644 crates/pm_auth/src/consts.rs create mode 100644 crates/pm_auth/src/core.rs create mode 100644 crates/pm_auth/src/core/errors.rs create mode 100644 crates/pm_auth/src/lib.rs create mode 100644 crates/pm_auth/src/types.rs create mode 100644 crates/pm_auth/src/types/api.rs create mode 100644 crates/pm_auth/src/types/api/auth_service.rs create mode 100644 crates/router/src/connector/signifyd.rs create mode 100644 crates/router/src/connector/signifyd/transformers.rs create mode 100644 crates/router/src/connector/signifyd/transformers/api.rs create mode 100644 crates/router/src/connector/signifyd/transformers/auth.rs create mode 100644 crates/router/src/core/connector_onboarding.rs create mode 100644 crates/router/src/core/connector_onboarding/paypal.rs create mode 100644 crates/router/src/core/fraud_check.rs create mode 100644 crates/router/src/core/fraud_check/flows.rs create mode 100644 crates/router/src/core/fraud_check/flows/checkout_flow.rs create mode 100644 crates/router/src/core/fraud_check/flows/fulfillment_flow.rs create mode 100644 crates/router/src/core/fraud_check/flows/record_return.rs create mode 100644 crates/router/src/core/fraud_check/flows/sale_flow.rs create mode 100644 crates/router/src/core/fraud_check/flows/transaction_flow.rs create mode 100644 crates/router/src/core/fraud_check/operation.rs create mode 100644 crates/router/src/core/fraud_check/operation/fraud_check_post.rs create mode 100644 crates/router/src/core/fraud_check/operation/fraud_check_pre.rs create mode 100644 crates/router/src/core/fraud_check/types.rs create mode 100644 crates/router/src/core/payments/flows/incremental_authorization_flow.rs create mode 100644 crates/router/src/core/payments/operations/payments_incremental_authorization.rs create mode 100644 crates/router/src/core/pm_auth.rs create mode 100644 crates/router/src/core/pm_auth/helpers.rs create mode 100644 crates/router/src/core/pm_auth/transformers.rs create mode 100644 crates/router/src/db/authorization.rs create mode 100644 crates/router/src/routes/connector_onboarding.rs create mode 100644 crates/router/src/routes/fraud_check.rs create mode 100644 crates/router/src/routes/pm_auth.rs create mode 100644 crates/router/src/services/pm_auth.rs create mode 100644 crates/router/src/types/api/connector_onboarding.rs create mode 100644 crates/router/src/types/api/connector_onboarding/paypal.rs create mode 100644 crates/router/src/types/api/fraud_check.rs create mode 100644 crates/router/src/types/fraud_check.rs create mode 100644 crates/router/src/types/pm_auth.rs create mode 100644 crates/router/src/types/storage/authorization.rs create mode 100644 crates/router/src/types/storage/fraud_check.rs create mode 100644 crates/router/src/utils/connector_onboarding.rs create mode 100644 crates/router/src/utils/connector_onboarding/paypal.rs create mode 100644 migrations/2023-11-30-170902_add-authorizations-table/down.sql create mode 100644 migrations/2023-11-30-170902_add-authorizations-table/up.sql create mode 100644 migrations/2023-12-01-090834_add-authorization_count-in-payment_intent/down.sql create mode 100644 migrations/2023-12-01-090834_add-authorization_count-in-payment_intent/up.sql diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3024477bac20..a911d26d8650 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -44,6 +44,60 @@ crates/router/src/core/routing.rs @juspay/hyperswitch-routing crates/router/src/core/payments/routing @juspay/hyperswitch-routing crates/router/src/core/payments/routing.rs @juspay/hyperswitch-routing +crates/api_models/src/connector_onboarding.rs @juspay/hyperswitch-dashboard +crates/api_models/src/user @juspay/hyperswitch-dashboard +crates/api_models/src/user.rs @juspay/hyperswitch-dashboard +crates/api_models/src/user_role.rs @juspay/hyperswitch-dashboard +crates/api_models/src/verify_connector.rs @juspay/hyperswitch-dashboard +crates/api_models/src/connector_onboarding.rs @juspay/hyperswitch-dashboard +crates/diesel_models/src/query/dashboard_metadata.rs @juspay/hyperswitch-dashboard +crates/diesel_models/src/query/user @juspay/hyperswitch-dashboard +crates/diesel_models/src/query/user_role.rs @juspay/hyperswitch-dashboard +crates/diesel_models/src/query/user.rs @juspay/hyperswitch-dashboard +crates/diesel_models/src/user @juspay/hyperswitch-dashboard +crates/diesel_models/src/user.rs @juspay/hyperswitch-dashboard +crates/diesel_models/src/user_role.rs @juspay/hyperswitch-dashboard +crates/router/src/consts/user.rs @juspay/hyperswitch-dashboard +crates/router/src/consts/user_role.rs @juspay/hyperswitch-dashboard +crates/router/src/core/connector_onboarding @juspay/hyperswitch-dashboard +crates/router/src/core/connector_onboarding.rs @juspay/hyperswitch-dashboard +crates/router/src/core/errors/user.rs @juspay/hyperswitch-dashboard +crates/router/src/core/errors/user @juspay/hyperswitch-dashboard +crates/router/src/core/user @juspay/hyperswitch-dashboard +crates/router/src/core/user.rs @juspay/hyperswitch-dashboard +crates/router/src/core/user_role.rs @juspay/hyperswitch-dashboard +crates/router/src/core/verify_connector.rs @juspay/hyperswitch-dashboard +crates/router/src/db/dashboard_metadata.rs @juspay/hyperswitch-dashboard +crates/router/src/db/user @juspay/hyperswitch-dashboard +crates/router/src/db/user.rs @juspay/hyperswitch-dashboard +crates/router/src/db/user_role.rs @juspay/hyperswitch-dashboard +crates/router/src/routes/connector_onboarding.rs @juspay/hyperswitch-dashboard +crates/router/src/routes/dummy_connector @juspay/hyperswitch-dashboard +crates/router/src/routes/dummy_connector.rs @juspay/hyperswitch-dashboard +crates/router/src/routes/user.rs @juspay/hyperswitch-dashboard +crates/router/src/routes/user_role.rs @juspay/hyperswitch-dashboard +crates/router/src/routes/verify_connector.rs @juspay/hyperswitch-dashboard +crates/router/src/services/authentication.rs @juspay/hyperswitch-dashboard +crates/router/src/services/authorization @juspay/hyperswitch-dashboard +crates/router/src/services/authorization.rs @juspay/hyperswitch-dashboard +crates/router/src/services/jwt.rs @juspay/hyperswitch-dashboard +crates/router/src/services/email/types.rs @juspay/hyperswitch-dashboard +crates/router/src/types/api/connector_onboarding @juspay/hyperswitch-dashboard +crates/router/src/types/api/connector_onboarding.rs @juspay/hyperswitch-dashboard +crates/router/src/types/api/verify_connector @juspay/hyperswitch-dashboard +crates/router/src/types/api/verify_connector.rs @juspay/hyperswitch-dashboard +crates/router/src/types/domain/user @juspay/hyperswitch-dashboard +crates/router/src/types/domain/user.rs @juspay/hyperswitch-dashboard +crates/router/src/types/storage/user.rs @juspay/hyperswitch-dashboard +crates/router/src/types/storage/user_role.rs @juspay/hyperswitch-dashboard +crates/router/src/types/storage/dashboard_metadata.rs @juspay/hyperswitch-dashboard +crates/router/src/utils/connector_onboarding @juspay/hyperswitch-dashboard +crates/router/src/utils/connector_onboarding.rs @juspay/hyperswitch-dashboard +crates/router/src/utils/user @juspay/hyperswitch-dashboard +crates/router/src/utils/user.rs @juspay/hyperswitch-dashboard +crates/router/src/utils/user_role.rs @juspay/hyperswitch-dashboard +crates/router/src/utils/verify_connector.rs @juspay/hyperswitch-dashboard + crates/router/src/scheduler/ @juspay/hyperswitch-process-tracker Dockerfile @juspay/hyperswitch-infra diff --git a/.github/workflows/CI-pr.yml b/.github/workflows/CI-pr.yml index ecb13f3c1a85..79cb352acbb8 100644 --- a/.github/workflows/CI-pr.yml +++ b/.github/workflows/CI-pr.yml @@ -203,6 +203,11 @@ jobs: else echo "test_utils_changes_exist=true" >> $GITHUB_ENV fi + if git diff --submodule=diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/pm_auth/; then + echo "pm_auth_changes_exist=false" >> $GITHUB_ENV + else + echo "pm_auth_changes_exist=true" >> $GITHUB_ENV + fi - name: Cargo hack api_models if: env.api_models_changes_exist == 'true' @@ -249,6 +254,11 @@ jobs: shell: bash run: cargo hack check --each-feature --no-dev-deps -p redis_interface + - name: Cargo hack pm_auth + if: env.pm_auth_changes_exist == 'true' + shell: bash + run: cargo hack check --each-feature --no-dev-deps -p pm_auth + - name: Cargo hack router if: env.router_changes_exist == 'true' shell: bash @@ -456,6 +466,11 @@ jobs: else echo "test_utils_changes_exist=true" >> $GITHUB_ENV fi + if git diff --submodule=diff --exit-code --quiet origin/$GITHUB_BASE_REF -- crates/pm_auth/; then + echo "pm_auth_changes_exist=false" >> $GITHUB_ENV + else + echo "pm_auth_changes_exist=true" >> $GITHUB_ENV + fi - name: Cargo hack api_models if: env.api_models_changes_exist == 'true' @@ -502,6 +517,11 @@ jobs: shell: bash run: cargo hack check --each-feature --no-dev-deps -p redis_interface + - name: Cargo hack pm_auth + if: env.pm_auth_changes_exist == 'true' + shell: bash + run: cargo hack check --each-feature --no-dev-deps -p pm_auth + - name: Cargo hack router if: env.router_changes_exist == 'true' shell: bash diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bbdac921fd7..6bfcc08d08ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,90 @@ All notable changes to HyperSwitch will be documented here. - - - +## 1.97.0 (2023-12-06) + +### Features + +- **Braintree:** Sync with Hyperswitch Reference ([#3037](https://github.com/juspay/hyperswitch/pull/3037)) ([`8a995ce`](https://github.com/juspay/hyperswitch/commit/8a995cefdf6806645383710c6f39d963da232e94)) +- **connector:** [BANKOFAMERICA] Implement Apple Pay ([#3061](https://github.com/juspay/hyperswitch/pull/3061)) ([`47c0383`](https://github.com/juspay/hyperswitch/commit/47c038300adad1c02e4c77d529c7cc2457cf3b91)) +- **metrics:** Add drainer delay metric ([#3034](https://github.com/juspay/hyperswitch/pull/3034)) ([`c6e2ee2`](https://github.com/juspay/hyperswitch/commit/c6e2ee29d9ee4fe54e6fa6f87c2fa065a290d258)) + +### Bug Fixes + +- **config:** Parse kafka brokers from env variable as sequence ([#3066](https://github.com/juspay/hyperswitch/pull/3066)) ([`84decd8`](https://github.com/juspay/hyperswitch/commit/84decd8126d306a5e1cf22b36e1378a73dc963f5)) +- Throw bad request while pushing duplicate data to redis ([#3016](https://github.com/juspay/hyperswitch/pull/3016)) ([`a2405e5`](https://github.com/juspay/hyperswitch/commit/a2405e56fbd84936a1afa6aa9f8f7e815267fbec)) +- Return url none on complete authorize ([#3067](https://github.com/juspay/hyperswitch/pull/3067)) ([`6eec06b`](https://github.com/juspay/hyperswitch/commit/6eec06b1d6ee9a00b374905e0ab9e425d0e41095)) + +### Miscellaneous Tasks + +- **codeowners:** Add codeowners for hyperswitch dashboard ([#3057](https://github.com/juspay/hyperswitch/pull/3057)) ([`cfafd5c`](https://github.com/juspay/hyperswitch/commit/cfafd5cd29857283d57731dda7c5a332a493f531)) + +**Full Changelog:** [`v1.96.0...v1.97.0`](https://github.com/juspay/hyperswitch/compare/v1.96.0...v1.97.0) + +- - - + + +## 1.96.0 (2023-12-05) + +### Features + +- **connector_onboarding:** Add Connector onboarding APIs ([#3050](https://github.com/juspay/hyperswitch/pull/3050)) ([`7bd6e05`](https://github.com/juspay/hyperswitch/commit/7bd6e05c0c05ebae9b82a6f410e61ca4409d088b)) +- **pm_list:** Add required fields for bancontact_card for Mollie, Adyen and Stripe ([#3035](https://github.com/juspay/hyperswitch/pull/3035)) ([`792e642`](https://github.com/juspay/hyperswitch/commit/792e642ad58f90bae3ddcea5e6cbc70e948d8e28)) +- **user:** Add email apis and new enums for metadata ([#3053](https://github.com/juspay/hyperswitch/pull/3053)) ([`1c3d260`](https://github.com/juspay/hyperswitch/commit/1c3d260dc3e18fbf6cbd5122122a6c73dceb39a3)) +- Implement FRM flows ([#2968](https://github.com/juspay/hyperswitch/pull/2968)) ([`055d838`](https://github.com/juspay/hyperswitch/commit/055d8383671f6b466297c177bcc770618c7da96a)) + +### Bug Fixes + +- Remove redundant call to populate_payment_data function ([#3054](https://github.com/juspay/hyperswitch/pull/3054)) ([`53df543`](https://github.com/juspay/hyperswitch/commit/53df543b7f1407a758232025b7de0fb527be8e86)) + +### Documentation + +- **test_utils:** Update postman docs ([#3055](https://github.com/juspay/hyperswitch/pull/3055)) ([`8b7a7aa`](https://github.com/juspay/hyperswitch/commit/8b7a7aa6494ff669e1f8bcc92a5160e422d6b26e)) + +**Full Changelog:** [`v1.95.0...v1.96.0`](https://github.com/juspay/hyperswitch/compare/v1.95.0...v1.96.0) + +- - - + + +## 1.95.0 (2023-12-05) + +### Features + +- **connector:** [BOA/CYBERSOURCE] Fix Status Mapping for Terminal St… ([#3031](https://github.com/juspay/hyperswitch/pull/3031)) ([`95876b0`](https://github.com/juspay/hyperswitch/commit/95876b0ce03e024edf77909502c53eb4e63a9855)) +- **pm_list:** Add required field for open_banking_uk for Adyen and Volt Connector ([#3032](https://github.com/juspay/hyperswitch/pull/3032)) ([`9d93533`](https://github.com/juspay/hyperswitch/commit/9d935332193dcc9f191a0a5a9e7405316794a418)) +- **router:** + - Add key_value to locker metrics ([#2995](https://github.com/juspay/hyperswitch/pull/2995)) ([`83fcd1a`](https://github.com/juspay/hyperswitch/commit/83fcd1a9deb106a44c8262923c7f1660b0c46bf2)) + - Add payments incremental authorization api ([#3038](https://github.com/juspay/hyperswitch/pull/3038)) ([`a0cfdd3`](https://github.com/juspay/hyperswitch/commit/a0cfdd3fb12f04b603f65551eac985c31e08da85)) +- **types:** Add email types for sending emails ([#3020](https://github.com/juspay/hyperswitch/pull/3020)) ([`c4bd47e`](https://github.com/juspay/hyperswitch/commit/c4bd47eca93a158c9daeeeb18afb1e735eea8c94)) +- **user:** + - Generate and delete sample data ([#2987](https://github.com/juspay/hyperswitch/pull/2987)) ([`092ec73`](https://github.com/juspay/hyperswitch/commit/092ec73b3c65ce6048d379383b078d643f0f35fc)) + - Add user_list and switch_list apis ([#3033](https://github.com/juspay/hyperswitch/pull/3033)) ([`ec15ddd`](https://github.com/juspay/hyperswitch/commit/ec15ddd0d0ed942fedec525406df3005d494b8d4)) +- Calculate surcharge for customer saved card list ([#3039](https://github.com/juspay/hyperswitch/pull/3039)) ([`daf0f09`](https://github.com/juspay/hyperswitch/commit/daf0f09f8e3293ee6a3599a25362d9171fc5b2e7)) + +### Bug Fixes + +- **connector:** [Paypal] Parse response for Cards with no 3DS check ([#3021](https://github.com/juspay/hyperswitch/pull/3021)) ([`d883cd1`](https://github.com/juspay/hyperswitch/commit/d883cd18972c5f9e8350e9a3f4e5cd56ec2c0787)) +- **pm_list:** [Trustpay]Update dynamic fields for trustpay blik ([#3042](https://github.com/juspay/hyperswitch/pull/3042)) ([`9274cef`](https://github.com/juspay/hyperswitch/commit/9274cefbdd29d2ac64baeea2fe504dff2472cb47)) +- **wasm:** Fix wasm function to return the categories for keys with their description respectively ([#3023](https://github.com/juspay/hyperswitch/pull/3023)) ([`2ac5b2c`](https://github.com/juspay/hyperswitch/commit/2ac5b2cd764c0aad53ac7c672dfcc9132fa5668f)) +- Use card bin to get additional card details ([#3036](https://github.com/juspay/hyperswitch/pull/3036)) ([`6c7d3a2`](https://github.com/juspay/hyperswitch/commit/6c7d3a2e8a047ff23b52b76792fe8f28d3b952a4)) +- Transform connector name to lowercase in connector integration script ([#3048](https://github.com/juspay/hyperswitch/pull/3048)) ([`298e362`](https://github.com/juspay/hyperswitch/commit/298e3627c379de5acfcafb074036754661801f1e)) +- Add fallback to reverselookup error ([#3025](https://github.com/juspay/hyperswitch/pull/3025)) ([`ba392f5`](https://github.com/juspay/hyperswitch/commit/ba392f58b2956d67e93a08853bcf2270a869be27)) + +### Refactors + +- **payment_methods:** Add support for passing card_cvc in payment_method_data object along with token ([#3024](https://github.com/juspay/hyperswitch/pull/3024)) ([`3ce04ab`](https://github.com/juspay/hyperswitch/commit/3ce04abae4eddfa27025368f5ef28987cccea43d)) +- **users:** Separate signup and signin ([#2921](https://github.com/juspay/hyperswitch/pull/2921)) ([`80efeb7`](https://github.com/juspay/hyperswitch/commit/80efeb76b1801529766978af1c06e2d2c7de66c0)) +- Create separate struct for surcharge details response ([#3027](https://github.com/juspay/hyperswitch/pull/3027)) ([`57591f8`](https://github.com/juspay/hyperswitch/commit/57591f819c7994099e76cff1affc7bcf3e45a031)) + +### Testing + +- **postman:** Update postman collection files ([`6e09bc9`](https://github.com/juspay/hyperswitch/commit/6e09bc9e2c4bbe14dcb70da4a438850b03b3254c)) + +**Full Changelog:** [`v1.94.0...v1.95.0`](https://github.com/juspay/hyperswitch/compare/v1.94.0...v1.95.0) + +- - - + + ## 1.94.0 (2023-12-01) ### Features diff --git a/Cargo.lock b/Cargo.lock index 3306a341bde4..e54725cdbaca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -405,6 +405,8 @@ dependencies = [ "common_utils", "error-stack", "euclid", + "frunk", + "frunk_core", "masking", "mime", "reqwest", @@ -2051,6 +2053,7 @@ dependencies = [ "async-trait", "common_enums", "common_utils", + "diesel_models", "error-stack", "masking", "serde", @@ -4436,6 +4439,27 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "pm_auth" +version = "0.1.0" +dependencies = [ + "api_models", + "async-trait", + "bytes 1.5.0", + "common_enums", + "common_utils", + "error-stack", + "http", + "masking", + "mime", + "router_derive", + "router_env", + "serde", + "serde_json", + "strum 0.24.1", + "thiserror", +] + [[package]] name = "png" version = "0.16.8" @@ -5120,6 +5144,7 @@ dependencies = [ "num_cpus", "once_cell", "openssl", + "pm_auth", "qrcode", "quick-xml 0.31.0", "rand 0.8.5", diff --git a/config/config.example.toml b/config/config.example.toml index 14318e9200e7..1ef0d343c898 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -122,7 +122,7 @@ kms_encrypted_recon_admin_api_key = "" # Base64-encoded (KMS encrypted) cipher # like card details [locker] host = "" # Locker host -host_rs = "" # Rust Locker host +host_rs = "" # Rust Locker host mock_locker = true # Emulate a locker locally using Postgres basilisk_host = "" # Basilisk host locker_signing_key_id = "1" # Key_id to sign basilisk hs locker @@ -215,6 +215,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" shift4.base_url = "https://api.shift4.com/" +signifyd.base_url = "https://api.signifyd.com/" square.base_url = "https://connect.squareupsandbox.com/" square.secondary_base_url = "https://pci-connect.squareupsandbox.com/" stax.base_url = "https://apiprod.fattlabs.com/" @@ -371,7 +372,7 @@ slack_invite_url = "https://www.example.com/" # Slack invite url for hyperswit discord_invite_url = "https://www.example.com/" # Discord invite url for hyperswitch [mandates.supported_payment_methods] -card.credit = { connector_list = "stripe,adyen" } # Mandate supported payment method type and connector for card +card.credit = { connector_list = "stripe,adyen,cybersource" } # Mandate supported payment method type and connector for card wallet.paypal = { connector_list = "adyen" } # Mandate supported payment method type and connector for wallets pay_later.klarna = { connector_list = "adyen" } # Mandate supported payment method type and connector for pay_later bank_debit.ach = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit @@ -460,6 +461,10 @@ apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY" #Private key [payment_link] sdk_url = "http://localhost:9090/dist/HyperLoader.js" +[payment_method_auth] +redis_expiry = 900 +pm_auth_key = "Some_pm_auth_key" + # Analytics configuration. [analytics] source = "sqlx" # The Analytics source/strategy to be used @@ -485,6 +490,24 @@ queue_strategy = "Fifo" # Add the queue strategy used by the database bb8 clie # TTL for KV in seconds ttl = 900 +[frm] +enabled = true + +[paypal_onboarding] +client_id = "paypal_client_id" # Client ID for PayPal onboarding +client_secret = "paypal_secret_key" # Secret key for PayPal onboarding +partner_id = "paypal_partner_id" # Partner ID for PayPal onboarding +enabled = true # Switch to enable or disable PayPal onboarding + +[frm] +enabled = true + +[paypal_onboarding] +client_id = "paypal_client_id" # Client ID for PayPal onboarding +client_secret = "paypal_secret_key" # Secret key for PayPal onboarding +partner_id = "paypal_partner_id" # Partner ID for PayPal onboarding +enabled = true # Switch to enable or disable PayPal onboarding + [events] source = "logs" # The event sink to push events supports kafka or logs (stdout) diff --git a/config/development.toml b/config/development.toml index fa5fddb0d60a..dc5423366247 100644 --- a/config/development.toml +++ b/config/development.toml @@ -189,6 +189,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" shift4.base_url = "https://api.shift4.com/" +signifyd.base_url = "https://api.signifyd.com/" square.base_url = "https://connect.squareupsandbox.com/" square.secondary_base_url = "https://pci-connect.squareupsandbox.com/" stax.base_url = "https://apiprod.fattlabs.com/" @@ -445,8 +446,8 @@ pay_later.klarna = { connector_list = "adyen" } wallet.google_pay = { connector_list = "stripe,adyen" } wallet.apple_pay = { connector_list = "stripe,adyen" } wallet.paypal = { connector_list = "adyen" } -card.credit = { connector_list = "stripe,adyen,authorizedotnet,globalpay,worldpay,multisafepay,nmi,nexinets,noon" } -card.debit = { connector_list = "stripe,adyen,authorizedotnet,globalpay,worldpay,multisafepay,nmi,nexinets,noon" } +card.credit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" } +card.debit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon" } bank_debit.ach = { connector_list = "gocardless"} bank_debit.becs = { connector_list = "gocardless"} bank_debit.sepa = { connector_list = "gocardless"} @@ -469,6 +470,10 @@ apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY" [payment_link] sdk_url = "http://localhost:9090/dist/HyperLoader.js" +[payment_method_auth] +redis_expiry = 900 +pm_auth_key = "Some_pm_auth_key" + [lock_settings] redis_lock_expiry_seconds = 180 # 3 * 60 seconds delay_between_retries_in_milliseconds = 500 @@ -476,6 +481,9 @@ delay_between_retries_in_milliseconds = 500 [kv_config] ttl = 900 # 15 * 60 seconds +[frm] +enabled = true + [events] source = "logs" @@ -504,4 +512,10 @@ port = 5432 dbname = "hyperswitch_db" pool_size = 5 connection_timeout = 10 -queue_strategy = "Fifo" \ No newline at end of file +queue_strategy = "Fifo" + +[connector_onboarding.paypal] +client_id = "" +client_secret = "" +partner_id = "" +enabled = true diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 6bc6fcd69ea6..cb30c28a7c52 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -129,6 +129,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" shift4.base_url = "https://api.shift4.com/" +signifyd.base_url = "https://api.signifyd.com/" square.base_url = "https://connect.squareupsandbox.com/" square.secondary_base_url = "https://pci-connect.squareupsandbox.com/" stax.base_url = "https://apiprod.fattlabs.com/" @@ -316,8 +317,8 @@ pay_later.klarna = {connector_list = "adyen"} wallet.google_pay = {connector_list = "stripe,adyen"} wallet.apple_pay = {connector_list = "stripe,adyen"} wallet.paypal = {connector_list = "adyen"} -card.credit = {connector_list = "stripe,adyen,authorizedotnet,globalpay,worldpay,multisafepay,nmi,nexinets,noon"} -card.debit = {connector_list = "stripe,adyen,authorizedotnet,globalpay,worldpay,multisafepay,nmi,nexinets,noon"} +card.credit = {connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon"} +card.debit = {connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon"} bank_debit.ach = { connector_list = "gocardless"} bank_debit.becs = { connector_list = "gocardless"} bank_debit.sepa = { connector_list = "gocardless"} @@ -329,6 +330,10 @@ payout_connector_list = "wise" [multiple_api_version_supported_connectors] supported_connectors = "braintree" +[payment_method_auth] +redis_expiry = 900 +pm_auth_key = "Some_pm_auth_key" + [lock_settings] redis_lock_expiry_seconds = 180 # 3 * 60 seconds delay_between_retries_in_milliseconds = 500 @@ -362,3 +367,15 @@ queue_strategy = "Fifo" [kv_config] ttl = 900 # 15 * 60 seconds + +[frm] +enabled = true + +[connector_onboarding.paypal] +client_id = "" +client_secret = "" +partner_id = "" +enabled = true + +[events] +source = "logs" diff --git a/crates/api_models/Cargo.toml b/crates/api_models/Cargo.toml index cb2e243745de..afba129b601e 100644 --- a/crates/api_models/Cargo.toml +++ b/crates/api_models/Cargo.toml @@ -8,7 +8,7 @@ readme = "README.md" license.workspace = true [features] -default = ["payouts"] +default = ["payouts", "frm"] business_profile_routing = [] connector_choice_bcompat = [] errors = ["dep:actix-web", "dep:reqwest"] @@ -17,6 +17,7 @@ connector_choice_mca_id = ["euclid/connector_choice_mca_id"] dummy_connector = ["euclid/dummy_connector", "common_enums/dummy_connector"] detailed_errors = [] payouts = [] +frm = [] [dependencies] actix-web = { version = "4.3.1", optional = true } @@ -29,6 +30,8 @@ strum = { version = "0.25", features = ["derive"] } time = { version = "0.3.21", features = ["serde", "serde-well-known", "std"] } url = { version = "2.4.0", features = ["serde"] } utoipa = { version = "3.3.0", features = ["preserve_order"] } +frunk = "0.4.1" +frunk_core = "0.4.1" # First party crates cards = { version = "0.1.0", path = "../cards" } diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index 6bb4fd4afa0f..d35b12152e91 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -879,7 +879,7 @@ pub struct PaymentMethodsEnabled { pub payment_method: common_enums::PaymentMethod, /// Subtype of payment method - #[schema(value_type = Option>,example = json!(["credit"]))] + #[schema(value_type = Option>,example = json!(["credit"]))] pub payment_method_types: Option>, } diff --git a/crates/api_models/src/connector_onboarding.rs b/crates/api_models/src/connector_onboarding.rs new file mode 100644 index 000000000000..759d3cb97f13 --- /dev/null +++ b/crates/api_models/src/connector_onboarding.rs @@ -0,0 +1,54 @@ +use super::{admin, enums}; + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +pub struct ActionUrlRequest { + pub connector: enums::Connector, + pub connector_id: String, + pub return_url: String, +} + +#[derive(serde::Serialize, Debug, Clone)] +#[serde(rename_all = "lowercase")] +pub enum ActionUrlResponse { + PayPal(PayPalActionUrlResponse), +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +pub struct OnboardingSyncRequest { + pub profile_id: String, + pub connector_id: String, + pub connector: enums::Connector, +} + +#[derive(serde::Serialize, Debug, Clone)] +pub struct PayPalActionUrlResponse { + pub action_url: String, +} + +#[derive(serde::Serialize, Debug, Clone)] +#[serde(rename_all = "lowercase")] +pub enum OnboardingStatus { + PayPal(PayPalOnboardingStatus), +} + +#[derive(serde::Serialize, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum PayPalOnboardingStatus { + AccountNotFound, + PaymentsNotReceivable, + PpcpCustomDenied, + MorePermissionsNeeded, + EmailNotVerified, + Success(PayPalOnboardingDone), + ConnectorIntegrated(admin::MerchantConnectorResponse), +} + +#[derive(serde::Serialize, Debug, Clone)] +pub struct PayPalOnboardingDone { + pub payer_id: String, +} + +#[derive(serde::Serialize, Debug, Clone)] +pub struct PayPalIntegrationDone { + pub connector_id: String, +} diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 535be4dfb159..215860540555 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + pub use common_enums::*; use utoipa::ToSchema; @@ -178,6 +180,36 @@ impl From for RoutableConnectors { } } +#[cfg(feature = "frm")] +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + PartialEq, + serde::Serialize, + serde::Deserialize, + strum::Display, + strum::EnumString, + ToSchema, +)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum FrmConnectors { + /// Signifyd Risk Manager. Official docs: https://docs.signifyd.com/ + Signifyd, +} + +#[cfg(feature = "frm")] +impl From for RoutableConnectors { + fn from(value: FrmConnectors) -> Self { + match value { + FrmConnectors::Signifyd => Self::Signifyd, + } + } +} + #[derive( Clone, Copy, @@ -470,3 +502,26 @@ pub enum LockerChoice { Basilisk, Tartarus, } + +#[derive( + Clone, + Copy, + Debug, + Eq, + PartialEq, + serde::Serialize, + serde::Deserialize, + strum::Display, + strum::EnumString, + frunk::LabelledGeneric, + ToSchema, +)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum PmAuthConnectors { + Plaid, +} + +pub fn convert_pm_auth_connector(connector_name: &str) -> Option { + PmAuthConnectors::from_str(connector_name).ok() +} diff --git a/crates/api_models/src/events.rs b/crates/api_models/src/events.rs index ac7cdeb83d94..457d3fde05b7 100644 --- a/crates/api_models/src/events.rs +++ b/crates/api_models/src/events.rs @@ -1,3 +1,4 @@ +pub mod connector_onboarding; pub mod customer; pub mod gsm; mod locker_migration; diff --git a/crates/api_models/src/events/connector_onboarding.rs b/crates/api_models/src/events/connector_onboarding.rs new file mode 100644 index 000000000000..998dc384d620 --- /dev/null +++ b/crates/api_models/src/events/connector_onboarding.rs @@ -0,0 +1,12 @@ +use common_utils::events::{ApiEventMetric, ApiEventsType}; + +use crate::connector_onboarding::{ + ActionUrlRequest, ActionUrlResponse, OnboardingStatus, OnboardingSyncRequest, +}; + +common_utils::impl_misc_api_event_type!( + ActionUrlRequest, + ActionUrlResponse, + OnboardingSyncRequest, + OnboardingStatus +); diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index 2f3336fc2777..f718dc1ca4dd 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -8,8 +8,9 @@ use crate::{ payments::{ PaymentIdType, PaymentListConstraints, PaymentListFilterConstraints, PaymentListFilters, PaymentListResponse, PaymentListResponseV2, PaymentsApproveRequest, PaymentsCancelRequest, - PaymentsCaptureRequest, PaymentsRejectRequest, PaymentsRequest, PaymentsResponse, - PaymentsRetrieveRequest, PaymentsStartRequest, RedirectionResponse, + PaymentsCaptureRequest, PaymentsIncrementalAuthorizationRequest, PaymentsRejectRequest, + PaymentsRequest, PaymentsResponse, PaymentsRetrieveRequest, PaymentsStartRequest, + RedirectionResponse, }, }; impl ApiEventMetric for PaymentsRetrieveRequest { @@ -149,3 +150,11 @@ impl ApiEventMetric for PaymentListResponseV2 { } impl ApiEventMetric for RedirectionResponse {} + +impl ApiEventMetric for PaymentsIncrementalAuthorizationRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.payment_id.clone(), + }) + } +} diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs index 8b7cd02c9350..92b675723964 100644 --- a/crates/api_models/src/events/user.rs +++ b/crates/api_models/src/events/user.rs @@ -6,11 +6,13 @@ use crate::user::{ dashboard_metadata::{ GetMetaDataRequest, GetMetaDataResponse, GetMultipleMetaDataPayload, SetMetaDataRequest, }, - ChangePasswordRequest, ConnectAccountRequest, ConnectAccountResponse, - CreateInternalUserRequest, GetUsersResponse, SwitchMerchantIdRequest, UserMerchantCreate, + AuthorizeResponse, ChangePasswordRequest, ConnectAccountRequest, CreateInternalUserRequest, + DashboardEntryResponse, ForgotPasswordRequest, GetUsersResponse, InviteUserRequest, + InviteUserResponse, ResetPasswordRequest, SignUpRequest, SignUpWithMerchantIdRequest, + SwitchMerchantIdRequest, UserMerchantCreate, VerifyEmailRequest, }; -impl ApiEventMetric for ConnectAccountResponse { +impl ApiEventMetric for DashboardEntryResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::User { merchant_id: self.merchant_id.clone(), @@ -19,9 +21,9 @@ impl ApiEventMetric for ConnectAccountResponse { } } -impl ApiEventMetric for ConnectAccountRequest {} - common_utils::impl_misc_api_event_type!( + SignUpRequest, + SignUpWithMerchantIdRequest, ChangePasswordRequest, GetMultipleMetaDataPayload, GetMetaDataResponse, @@ -30,7 +32,14 @@ common_utils::impl_misc_api_event_type!( SwitchMerchantIdRequest, CreateInternalUserRequest, UserMerchantCreate, - GetUsersResponse + GetUsersResponse, + AuthorizeResponse, + ConnectAccountRequest, + ForgotPasswordRequest, + ResetPasswordRequest, + InviteUserRequest, + InviteUserResponse, + VerifyEmailRequest ); #[cfg(feature = "dummy_connector")] diff --git a/crates/api_models/src/lib.rs b/crates/api_models/src/lib.rs index 056888839a54..935944cf74c2 100644 --- a/crates/api_models/src/lib.rs +++ b/crates/api_models/src/lib.rs @@ -5,6 +5,7 @@ pub mod api_keys; pub mod bank_accounts; pub mod cards_info; pub mod conditional_configs; +pub mod connector_onboarding; pub mod currency; pub mod customers; pub mod disputes; @@ -22,6 +23,7 @@ pub mod payment_methods; pub mod payments; #[cfg(feature = "payouts")] pub mod payouts; +pub mod pm_auth; pub mod refunds; pub mod routing; pub mod surcharge_decision_configs; diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index dfb8e8999771..85b0adefca5f 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -2,11 +2,13 @@ use std::collections::HashMap; use cards::CardNumber; use common_utils::{ - consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, crypto::OptionalEncryptableName, pii, - types::Percentage, + consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, + crypto::OptionalEncryptableName, + pii, + types::{Percentage, Surcharge}, }; use serde::de; -use utoipa::ToSchema; +use utoipa::{schema, ToSchema}; #[cfg(feature = "payouts")] use crate::payouts; @@ -14,7 +16,7 @@ use crate::{ admin, customers::CustomerId, enums as api_enums, - payments::{self, BankCodeResponse, RequestSurchargeDetails}, + payments::{self, BankCodeResponse}, }; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] @@ -262,19 +264,6 @@ pub struct CardNetworkTypes { pub card_network: api_enums::CardNetwork, /// surcharge details for this card network - #[schema(example = r#" - { - "surcharge": { - "type": "rate", - "value": { - "percentage": 2.5 - } - }, - "tax_on_surcharge": { - "percentage": 1.5 - } - } - "#)] pub surcharge_details: Option, /// The list of eligible connectors for a given card network @@ -311,145 +300,59 @@ pub struct ResponsePaymentMethodTypes { pub required_fields: Option>, /// surcharge details for this payment method type if exists - #[schema(example = r#" - { - "surcharge": { - "type": "rate", - "value": { - "percentage": 2.5 - } - }, - "tax_on_surcharge": { - "percentage": 1.5 - } - } - "#)] pub surcharge_details: Option, /// auth service connector label for this payment method type, if exists pub pm_auth_connector: Option, } -#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] + +#[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)] #[serde(rename_all = "snake_case")] pub struct SurchargeDetailsResponse { /// surcharge value - pub surcharge: Surcharge, + pub surcharge: SurchargeResponse, /// tax on surcharge value - pub tax_on_surcharge: Option>, + pub tax_on_surcharge: Option, /// surcharge amount for this payment - pub surcharge_amount: i64, + pub display_surcharge_amount: f64, /// tax on surcharge amount for this payment - pub tax_on_surcharge_amount: i64, + pub display_tax_on_surcharge_amount: f64, + /// sum of display_surcharge_amount and display_tax_on_surcharge_amount + pub display_total_surcharge_amount: f64, /// sum of original amount, - pub final_amount: i64, + pub display_final_amount: f64, } -impl SurchargeDetailsResponse { - pub fn is_request_surcharge_matching( - &self, - request_surcharge_details: RequestSurchargeDetails, - ) -> bool { - request_surcharge_details.surcharge_amount == self.surcharge_amount - && request_surcharge_details.tax_amount.unwrap_or(0) == self.tax_on_surcharge_amount - } - pub fn get_total_surcharge_amount(&self) -> i64 { - self.surcharge_amount + self.tax_on_surcharge_amount - } +#[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case", tag = "type", content = "value")] +pub enum SurchargeResponse { + /// Fixed Surcharge value + Fixed(i64), + /// Surcharge percentage + Rate(SurchargePercentage), } -#[derive(Clone, Debug)] -pub struct SurchargeMetadata { - surcharge_results: HashMap< - ( - common_enums::PaymentMethod, - common_enums::PaymentMethodType, - Option, - ), - SurchargeDetailsResponse, - >, - pub payment_attempt_id: String, -} - -impl SurchargeMetadata { - pub fn new(payment_attempt_id: String) -> Self { - Self { - surcharge_results: HashMap::new(), - payment_attempt_id, - } - } - pub fn is_empty_result(&self) -> bool { - self.surcharge_results.is_empty() - } - pub fn get_surcharge_results_size(&self) -> usize { - self.surcharge_results.len() - } - pub fn insert_surcharge_details( - &mut self, - payment_method: &common_enums::PaymentMethod, - payment_method_type: &common_enums::PaymentMethodType, - card_network: Option<&common_enums::CardNetwork>, - surcharge_details: SurchargeDetailsResponse, - ) { - let key = ( - payment_method.to_owned(), - payment_method_type.to_owned(), - card_network.cloned(), - ); - self.surcharge_results.insert(key, surcharge_details); - } - pub fn get_surcharge_details( - &self, - payment_method: &common_enums::PaymentMethod, - payment_method_type: &common_enums::PaymentMethodType, - card_network: Option<&common_enums::CardNetwork>, - ) -> Option<&SurchargeDetailsResponse> { - let key = &( - payment_method.to_owned(), - payment_method_type.to_owned(), - card_network.cloned(), - ); - self.surcharge_results.get(key) - } - pub fn get_surcharge_metadata_redis_key(payment_attempt_id: &str) -> String { - format!("surcharge_metadata_{}", payment_attempt_id) - } - pub fn get_individual_surcharge_key_value_pairs( - &self, - ) -> Vec<(String, SurchargeDetailsResponse)> { - self.surcharge_results - .iter() - .map(|((pm, pmt, card_network), surcharge_details)| { - let key = - Self::get_surcharge_details_redis_hashset_key(pm, pmt, card_network.as_ref()); - (key, surcharge_details.to_owned()) - }) - .collect() - } - pub fn get_surcharge_details_redis_hashset_key( - payment_method: &common_enums::PaymentMethod, - payment_method_type: &common_enums::PaymentMethodType, - card_network: Option<&common_enums::CardNetwork>, - ) -> String { - if let Some(card_network) = card_network { - format!( - "{}_{}_{}", - payment_method, payment_method_type, card_network - ) - } else { - format!("{}_{}", payment_method, payment_method_type) +impl From for SurchargeResponse { + fn from(value: Surcharge) -> Self { + match value { + Surcharge::Fixed(amount) => Self::Fixed(amount), + Surcharge::Rate(percentage) => Self::Rate(percentage.into()), } } } -#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] -#[serde(rename_all = "snake_case", tag = "type", content = "value")] -pub enum Surcharge { - /// Fixed Surcharge value - Fixed(i64), - /// Surcharge percentage - Rate(Percentage), +#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)] +pub struct SurchargePercentage { + percentage: f32, } +impl From> for SurchargePercentage { + fn from(value: Percentage) -> Self { + Self { + percentage: value.get_percentage(), + } + } +} /// Required fields info used while listing the payment_method_data #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq, ToSchema, Hash)] pub struct RequiredFieldInfo { @@ -510,8 +413,11 @@ impl ResponsePaymentMethodIntermediate { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema, PartialEq, Eq, Hash)] pub struct RequestPaymentMethodTypes { + #[schema(value_type = PaymentMethodType)] pub payment_method_type: api_enums::PaymentMethodType, + #[schema(value_type = Option)] pub payment_experience: Option, + #[schema(value_type = Option>)] pub card_networks: Option>, /// List of currencies accepted or has the processing capabilities of the processor #[schema(example = json!( @@ -519,7 +425,7 @@ pub struct RequestPaymentMethodTypes { "type": "specific_accepted", "list": ["USD", "INR"] } - ))] + ), value_type = Option)] pub accepted_currencies: Option, /// List of Countries accepted or has the processing capabilities of the processor @@ -528,7 +434,7 @@ pub struct RequestPaymentMethodTypes { "type": "specific_accepted", "list": ["UK", "AU"] } - ))] + ), value_type = Option)] pub accepted_countries: Option, /// Minimum amount supported by the processor. To be represented in the lowest denomination of the target currency (For example, for USD it should be in cents) @@ -818,6 +724,9 @@ pub struct CustomerPaymentMethod { #[schema(example = json!({"mask": "0000"}))] pub bank: Option, + /// Surcharge details for this saved card + pub surcharge_details: Option, + /// Whether this payment method requires CVV to be collected #[schema(example = true)] pub requires_cvv: bool, diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index fe5ed417f350..93c97cbd443c 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -16,7 +16,6 @@ use crate::{ admin, disputes, enums::{self as api_enums}, ephemeral_key::EphemeralKeyCreateResponse, - payment_methods::{Surcharge, SurchargeDetailsResponse}, refunds, }; @@ -204,8 +203,9 @@ pub struct PaymentsRequest { #[schema(example = "187282ab-40ef-47a9-9206-5099ba31e432")] pub payment_token: Option, - /// This is used when payment is to be confirmed and the card is not saved - #[schema(value_type = Option)] + /// This is used when payment is to be confirmed and the card is not saved. + /// This field will be deprecated soon, use the CardToken object instead + #[schema(value_type = Option, deprecated)] pub card_cvc: Option>, /// The shipping address for the payment @@ -339,17 +339,6 @@ impl RequestSurchargeDetails { pub fn is_surcharge_zero(&self) -> bool { self.surcharge_amount == 0 && self.tax_amount.unwrap_or(0) == 0 } - pub fn get_surcharge_details_object(&self, original_amount: i64) -> SurchargeDetailsResponse { - let surcharge_amount = self.surcharge_amount; - let tax_on_surcharge_amount = self.tax_amount.unwrap_or(0); - SurchargeDetailsResponse { - surcharge: Surcharge::Fixed(self.surcharge_amount), - tax_on_surcharge: None, - surcharge_amount, - tax_on_surcharge_amount, - final_amount: original_amount + surcharge_amount + tax_on_surcharge_amount, - } - } pub fn get_total_surcharge_amount(&self) -> i64 { self.surcharge_amount + self.tax_amount.unwrap_or(0) } @@ -720,12 +709,43 @@ pub struct Card { pub nick_name: Option>, } -#[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +impl Card { + fn apply_additional_card_info(&self, additional_card_info: AdditionalCardInfo) -> Self { + Self { + card_number: self.card_number.clone(), + card_exp_month: self.card_exp_month.clone(), + card_exp_year: self.card_exp_year.clone(), + card_holder_name: self.card_holder_name.clone(), + card_cvc: self.card_cvc.clone(), + card_issuer: self + .card_issuer + .clone() + .or(additional_card_info.card_issuer), + card_network: self + .card_network + .clone() + .or(additional_card_info.card_network), + card_type: self.card_type.clone().or(additional_card_info.card_type), + card_issuing_country: self + .card_issuing_country + .clone() + .or(additional_card_info.card_issuing_country), + bank_code: self.bank_code.clone().or(additional_card_info.bank_code), + nick_name: self.nick_name.clone(), + } + } +} + +#[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema, Default)] #[serde(rename_all = "snake_case")] pub struct CardToken { /// The card holder's name #[schema(value_type = String, example = "John Test")] pub card_holder_name: Option>, + + /// The CVC number for the card + #[schema(value_type = Option)] + pub card_cvc: Option>, } #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] @@ -889,6 +909,21 @@ impl PaymentMethodData { | Self::CardToken(_) => None, } } + pub fn apply_additional_payment_data( + &self, + additional_payment_data: AdditionalPaymentData, + ) -> Self { + if let AdditionalPaymentData::Card(additional_card_info) = additional_payment_data { + match self { + Self::Card(card) => { + Self::Card(card.apply_additional_card_info(*additional_card_info)) + } + _ => self.to_owned(), + } + } else { + self.to_owned() + } + } } pub trait GetPaymentMethodType { @@ -2216,6 +2251,12 @@ pub struct PaymentsResponse { /// If true incremental authorization can be performed on this payment pub incremental_authorization_allowed: Option, + + /// Total number of authorizations happened in an incremental_authorization payment + pub authorization_count: Option, + + /// List of incremental authorizations happened to the payment + pub incremental_authorizations: Option>, } #[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)] @@ -2284,6 +2325,24 @@ pub struct PaymentListResponse { // The list of payments response objects pub data: Vec, } + +#[derive(Setter, Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)] +pub struct IncrementalAuthorizationResponse { + /// The unique identifier of authorization + pub authorization_id: String, + /// Amount the authorization has been made for + pub amount: i64, + #[schema(value_type= AuthorizationStatus)] + /// The status of the authorization + pub status: common_enums::AuthorizationStatus, + /// Error code sent by the connector for authorization + pub error_code: Option, + /// Error message sent by the connector for authorization + pub error_message: Option, + /// Previously authorized amount for the payment + pub previously_authorized_amount: i64, +} + #[derive(Clone, Debug, serde::Serialize)] pub struct PaymentListResponseV2 { /// The number of payments included in the list for given constraints @@ -2992,6 +3051,18 @@ pub struct PaymentsCancelRequest { pub merchant_connector_details: Option, } +#[derive(Default, Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] +pub struct PaymentsIncrementalAuthorizationRequest { + /// The identifier for the payment + #[serde(skip)] + pub payment_id: String, + /// The total amount including previously authorized amount and additional amount + #[schema(value_type = i64, example = 6540)] + pub amount: i64, + /// Reason for incremental authorization + pub reason: Option, +} + #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] pub struct PaymentsApproveRequest { /// The identifier for the payment diff --git a/crates/api_models/src/pm_auth.rs b/crates/api_models/src/pm_auth.rs new file mode 100644 index 000000000000..7044bd8d3352 --- /dev/null +++ b/crates/api_models/src/pm_auth.rs @@ -0,0 +1,57 @@ +use common_enums::{PaymentMethod, PaymentMethodType}; +use common_utils::{ + events::{ApiEventMetric, ApiEventsType}, + impl_misc_api_event_type, +}; + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub struct LinkTokenCreateRequest { + pub language: Option, // optional language field to be passed + pub client_secret: Option, // client secret to be passed in req body + pub payment_id: String, // payment_id to be passed in req body for redis pm_auth connector name fetch + pub payment_method: PaymentMethod, // payment_method to be used for filtering pm_auth connector + pub payment_method_type: PaymentMethodType, // payment_method_type to be used for filtering pm_auth connector +} + +#[derive(Debug, Clone, serde::Serialize)] +pub struct LinkTokenCreateResponse { + pub link_token: String, // link_token received in response + pub connector: String, // pm_auth connector name in response +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] + +pub struct ExchangeTokenCreateRequest { + pub public_token: String, + pub client_secret: Option, + pub payment_id: String, + pub payment_method: PaymentMethod, + pub payment_method_type: PaymentMethodType, +} + +#[derive(Debug, Clone, serde::Serialize)] +pub struct ExchangeTokenCreateResponse { + pub access_token: String, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PaymentMethodAuthConfig { + pub enabled_payment_methods: Vec, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PaymentMethodAuthConnectorChoice { + pub payment_method: PaymentMethod, + pub payment_method_type: PaymentMethodType, + pub connector_name: String, + pub mca_id: String, +} + +impl_misc_api_event_type!( + LinkTokenCreateRequest, + LinkTokenCreateResponse, + ExchangeTokenCreateRequest, + ExchangeTokenCreateResponse +); diff --git a/crates/api_models/src/refunds.rs b/crates/api_models/src/refunds.rs index 6fe8be8b5291..e89de9c58934 100644 --- a/crates/api_models/src/refunds.rs +++ b/crates/api_models/src/refunds.rs @@ -174,7 +174,7 @@ pub struct RefundListMetaData { pub currency: Vec, /// The list of available refund status filters #[schema(value_type = Vec)] - pub status: Vec, + pub refund_status: Vec, } /// The status for refunds diff --git a/crates/api_models/src/surcharge_decision_configs.rs b/crates/api_models/src/surcharge_decision_configs.rs index 3ebf8f42744e..0777bde85de0 100644 --- a/crates/api_models/src/surcharge_decision_configs.rs +++ b/crates/api_models/src/surcharge_decision_configs.rs @@ -7,21 +7,21 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -pub struct SurchargeDetails { - pub surcharge: Surcharge, +pub struct SurchargeDetailsOutput { + pub surcharge: SurchargeOutput, pub tax_on_surcharge: Option>, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case", tag = "type", content = "value")] -pub enum Surcharge { - Fixed(i64), +pub enum SurchargeOutput { + Fixed { amount: i64 }, Rate(Percentage), } #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct SurchargeDecisionConfigs { - pub surcharge_details: Option, + pub surcharge_details: Option, } impl EuclidDirFilter for SurchargeDecisionConfigs { const ALLOWED: &'static [DirKeyKind] = &[ @@ -30,7 +30,6 @@ impl EuclidDirFilter for SurchargeDecisionConfigs { DirKeyKind::PaymentAmount, DirKeyKind::PaymentCurrency, DirKeyKind::BillingCountry, - DirKeyKind::CardType, DirKeyKind::CardNetwork, DirKeyKind::PayLaterType, DirKeyKind::WalletType, diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index 36d730f5118e..10d8411f8e70 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -7,13 +7,25 @@ pub mod dashboard_metadata; pub mod sample_data; #[derive(serde::Deserialize, Debug, Clone, serde::Serialize)] -pub struct ConnectAccountRequest { +pub struct SignUpWithMerchantIdRequest { + pub name: Secret, pub email: pii::Email, pub password: Secret, + pub company_name: String, } +pub type SignUpWithMerchantIdResponse = AuthorizeResponse; + +#[derive(serde::Deserialize, Debug, Clone, serde::Serialize)] +pub struct SignUpRequest { + pub email: pii::Email, + pub password: Secret, +} + +pub type SignUpResponse = DashboardEntryResponse; + #[derive(serde::Serialize, Debug, Clone)] -pub struct ConnectAccountResponse { +pub struct DashboardEntryResponse { pub token: Secret, pub merchant_id: String, pub name: Secret, @@ -25,17 +37,64 @@ pub struct ConnectAccountResponse { pub user_id: String, } +pub type SignInRequest = SignUpRequest; + +pub type SignInResponse = DashboardEntryResponse; + +#[derive(serde::Deserialize, Debug, Clone, serde::Serialize)] +pub struct ConnectAccountRequest { + pub email: pii::Email, +} + +pub type ConnectAccountResponse = AuthorizeResponse; + +#[derive(serde::Serialize, Debug, Clone)] +pub struct AuthorizeResponse { + pub is_email_sent: bool, + //this field is added for audit/debug reasons + #[serde(skip_serializing)] + pub user_id: String, + //this field is added for audit/debug reasons + #[serde(skip_serializing)] + pub merchant_id: String, +} + #[derive(serde::Deserialize, Debug, serde::Serialize)] pub struct ChangePasswordRequest { pub new_password: Secret, pub old_password: Secret, } +#[derive(serde::Deserialize, Debug, serde::Serialize)] +pub struct ForgotPasswordRequest { + pub email: pii::Email, +} + +#[derive(serde::Deserialize, Debug, serde::Serialize)] +pub struct ResetPasswordRequest { + pub token: Secret, + pub password: Secret, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct InviteUserRequest { + pub email: pii::Email, + pub name: Secret, + pub role_id: String, +} + +#[derive(Debug, serde::Serialize)] +pub struct InviteUserResponse { + pub is_email_sent: bool, +} + #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct SwitchMerchantIdRequest { pub merchant_id: String, } +pub type SwitchMerchantResponse = DashboardEntryResponse; + #[derive(serde::Deserialize, Debug, serde::Serialize)] pub struct CreateInternalUserRequest { pub name: Secret, @@ -62,3 +121,10 @@ pub struct UserDetails { #[serde(with = "common_utils::custom_serde::iso8601")] pub last_modified_at: time::PrimitiveDateTime, } + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct VerifyEmailRequest { + pub token: Secret, +} + +pub type VerifyEmailResponse = DashboardEntryResponse; diff --git a/crates/api_models/src/user/dashboard_metadata.rs b/crates/api_models/src/user/dashboard_metadata.rs index 04cda3bd7075..11588bbfbafe 100644 --- a/crates/api_models/src/user/dashboard_metadata.rs +++ b/crates/api_models/src/user/dashboard_metadata.rs @@ -1,3 +1,5 @@ +use common_enums::CountryAlpha2; +use common_utils::pii; use masking::Secret; use strum::EnumString; @@ -12,8 +14,11 @@ pub enum SetMetaDataRequest { ConfiguredRouting(ConfiguredRouting), TestPayment(TestPayment), IntegrationMethod(IntegrationMethod), + ConfigurationType(ConfigurationType), IntegrationCompleted, SPRoutingConfigured(ConfiguredRouting), + Feedback(Feedback), + ProdIntent(ProdIntent), SPTestPayment, DownloadWoocom, ConfigureWoocom, @@ -49,10 +54,38 @@ pub struct TestPayment { pub payment_id: String, } -#[derive(Debug, serde::Deserialize, serde::Serialize)] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct IntegrationMethod { pub integration_type: String, } +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub enum ConfigurationType { + Single, + Multiple, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct Feedback { + pub email: pii::Email, + pub description: Option, + pub rating: Option, + pub category: Option, +} +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct ProdIntent { + pub legal_business_name: Option, + pub business_label: Option, + pub business_location: Option, + pub display_name: Option, + pub poc_email: Option, + pub business_type: Option, + pub business_identifier: Option, + pub business_website: Option, + pub poc_name: Option, + pub poc_contact: Option, + pub comments: Option, + pub is_completed: bool, +} #[derive(Debug, serde::Deserialize, EnumString, serde::Serialize)] pub enum GetMetaDataRequest { @@ -65,10 +98,13 @@ pub enum GetMetaDataRequest { ConfiguredRouting, TestPayment, IntegrationMethod, + ConfigurationType, IntegrationCompleted, StripeConnected, PaypalConnected, SPRoutingConfigured, + Feedback, + ProdIntent, SPTestPayment, DownloadWoocom, ConfigureWoocom, @@ -98,10 +134,13 @@ pub enum GetMetaDataResponse { ConfiguredRouting(Option), TestPayment(Option), IntegrationMethod(Option), + ConfigurationType(Option), IntegrationCompleted(bool), StripeConnected(Option), PaypalConnected(Option), SPRoutingConfigured(Option), + Feedback(Option), + ProdIntent(Option), SPTestPayment(bool), DownloadWoocom(bool), ConfigureWoocom(bool), diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 8da4a2da54cc..980f98db1519 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -146,6 +146,7 @@ pub enum RoutableConnectors { Prophetpay, Rapyd, Shift4, + Signifyd, Square, Stax, Stripe, @@ -246,6 +247,32 @@ pub enum CaptureStatus { Failed, } +#[derive( + Default, + Clone, + Debug, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumString, + ToSchema, + Hash, +)] +#[router_derive::diesel_enum(storage_type = "text")] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum AuthorizationStatus { + Success, + Failure, + // Processing state is before calling connector + #[default] + Processing, + // Requires merchant action + Unresolved, +} + #[derive( Clone, Copy, diff --git a/crates/common_utils/src/events.rs b/crates/common_utils/src/events.rs index 14b8d4de1c36..c9efbb73c208 100644 --- a/crates/common_utils/src/events.rs +++ b/crates/common_utils/src/events.rs @@ -45,6 +45,7 @@ pub enum ApiEventsType { // TODO: This has to be removed once the corresponding apiEventTypes are created Miscellaneous, RustLocker, + FraudCheck, } impl ApiEventMetric for serde_json::Value {} diff --git a/crates/common_utils/src/lib.rs b/crates/common_utils/src/lib.rs index 62428dccfb6a..0ac8e886bc06 100644 --- a/crates/common_utils/src/lib.rs +++ b/crates/common_utils/src/lib.rs @@ -10,6 +10,7 @@ pub mod errors; pub mod events; pub mod ext_traits; pub mod fp_utils; +pub mod macros; pub mod pii; #[allow(missing_docs)] // Todo: add docs pub mod request; diff --git a/crates/common_utils/src/macros.rs b/crates/common_utils/src/macros.rs new file mode 100644 index 000000000000..9d41569384f1 --- /dev/null +++ b/crates/common_utils/src/macros.rs @@ -0,0 +1,92 @@ +#![allow(missing_docs)] + +#[macro_export] +macro_rules! newtype_impl { + ($is_pub:vis, $name:ident, $ty_path:path) => { + impl std::ops::Deref for $name { + type Target = $ty_path; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl std::ops::DerefMut for $name { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + impl From<$ty_path> for $name { + fn from(ty: $ty_path) -> Self { + Self(ty) + } + } + + impl $name { + pub fn into_inner(self) -> $ty_path { + self.0 + } + } + }; +} + +#[macro_export] +macro_rules! newtype { + ($is_pub:vis $name:ident = $ty_path:path) => { + $is_pub struct $name(pub $ty_path); + + $crate::newtype_impl!($is_pub, $name, $ty_path); + }; + + ($is_pub:vis $name:ident = $ty_path:path, derives = ($($trt:path),*)) => { + #[derive($($trt),*)] + $is_pub struct $name(pub $ty_path); + + $crate::newtype_impl!($is_pub, $name, $ty_path); + }; +} + +#[macro_export] +macro_rules! async_spawn { + ($t:block) => { + tokio::spawn(async move { $t }); + }; +} + +#[macro_export] +macro_rules! fallback_reverse_lookup_not_found { + ($a:expr,$b:expr) => { + match $a { + Ok(res) => res, + Err(err) => { + router_env::logger::error!(reverse_lookup_fallback = %err); + match err.current_context() { + errors::StorageError::ValueNotFound(_) => return $b, + errors::StorageError::DatabaseError(data_err) => { + match data_err.current_context() { + diesel_models::errors::DatabaseError::NotFound => return $b, + _ => return Err(err) + } + } + _=> return Err(err) + } + } + }; + }; +} + +#[macro_export] +macro_rules! collect_missing_value_keys { + [$(($key:literal, $option:expr)),+] => { + { + let mut keys: Vec<&'static str> = Vec::new(); + $( + if $option.is_none() { + keys.push($key); + } + )* + keys + } + }; +} diff --git a/crates/common_utils/src/request.rs b/crates/common_utils/src/request.rs index ea96f38d04c6..0b2cd691d6dd 100644 --- a/crates/common_utils/src/request.rs +++ b/crates/common_utils/src/request.rs @@ -17,6 +17,7 @@ pub enum Method { Post, Put, Delete, + Patch, } #[derive(Deserialize, Serialize, Debug)] diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs index 111f0f43c0f2..cf94f2fe26ce 100644 --- a/crates/common_utils/src/types.rs +++ b/crates/common_utils/src/types.rs @@ -2,7 +2,10 @@ use error_stack::{IntoReport, ResultExt}; use serde::{de::Visitor, Deserialize, Deserializer}; -use crate::errors::{CustomResult, PercentageError}; +use crate::{ + consts, + errors::{CustomResult, PercentageError}, +}; /// Represents Percentage Value between 0 and 100 both inclusive #[derive(Clone, Default, Debug, PartialEq, serde::Serialize)] @@ -136,3 +139,13 @@ impl<'de, const PRECISION: u8> Deserialize<'de> for Percentage { data.deserialize_map(PercentageVisitor:: {}) } } + +/// represents surcharge type and value +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "snake_case", tag = "type", content = "value")] +pub enum Surcharge { + /// Fixed Surcharge value + Fixed(i64), + /// Surcharge percentage + Rate(Percentage<{ consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH }>), +} diff --git a/crates/data_models/Cargo.toml b/crates/data_models/Cargo.toml index 857d53b6999e..a86dc3070b4d 100644 --- a/crates/data_models/Cargo.toml +++ b/crates/data_models/Cargo.toml @@ -17,6 +17,7 @@ api_models = { version = "0.1.0", path = "../api_models" } common_enums = { version = "0.1.0", path = "../common_enums" } common_utils = { version = "0.1.0", path = "../common_utils" } masking = { version = "0.1.0", path = "../masking" } +diesel_models = { version = "0.1.0", path = "../diesel_models", features = ["kv_store"] } # Third party deps async-trait = "0.1.68" diff --git a/crates/data_models/src/errors.rs b/crates/data_models/src/errors.rs index 4f8229ea0c9b..9616a3a944ca 100644 --- a/crates/data_models/src/errors.rs +++ b/crates/data_models/src/errors.rs @@ -1,3 +1,5 @@ +use diesel_models::errors::DatabaseError; + pub type StorageResult = error_stack::Result; #[derive(Debug, thiserror::Error)] @@ -6,7 +8,7 @@ pub enum StorageError { InitializationError, // TODO: deprecate this error type to use a domain error instead #[error("DatabaseError: {0:?}")] - DatabaseError(String), + DatabaseError(error_stack::Report), #[error("ValueNotFound: {0}")] ValueNotFound(String), #[error("DuplicateValue: {entity} already exists {key:?}")] diff --git a/crates/data_models/src/payments.rs b/crates/data_models/src/payments.rs index af2076bfa10d..7a4787fcf0a0 100644 --- a/crates/data_models/src/payments.rs +++ b/crates/data_models/src/payments.rs @@ -52,4 +52,5 @@ pub struct PaymentIntent { pub surcharge_applicable: Option, pub request_incremental_authorization: storage_enums::RequestIncrementalAuthorization, pub incremental_authorization_allowed: Option, + pub authorization_count: Option, } diff --git a/crates/data_models/src/payments/payment_attempt.rs b/crates/data_models/src/payments/payment_attempt.rs index 44aa48b142ad..f7b849f1d4e1 100644 --- a/crates/data_models/src/payments/payment_attempt.rs +++ b/crates/data_models/src/payments/payment_attempt.rs @@ -359,6 +359,10 @@ pub enum PaymentAttemptUpdate { connector: Option, updated_by: String, }, + IncrementalAuthorizationAmountUpdate { + amount: i64, + amount_capturable: i64, + }, } impl ForeignIDRef for PaymentAttempt { diff --git a/crates/data_models/src/payments/payment_intent.rs b/crates/data_models/src/payments/payment_intent.rs index d8f927a4e2c5..d7edcfdf1791 100644 --- a/crates/data_models/src/payments/payment_intent.rs +++ b/crates/data_models/src/payments/payment_intent.rs @@ -109,6 +109,7 @@ pub struct PaymentIntentNew { pub surcharge_applicable: Option, pub request_incremental_authorization: storage_enums::RequestIncrementalAuthorization, pub incremental_authorization_allowed: Option, + pub authorization_count: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -186,6 +187,12 @@ pub enum PaymentIntentUpdate { surcharge_applicable: bool, updated_by: String, }, + IncrementalAuthorizationAmountUpdate { + amount: i64, + }, + AuthorizationCountUpdate { + authorization_count: i32, + }, } #[derive(Clone, Debug, Default)] @@ -218,6 +225,7 @@ pub struct PaymentIntentUpdateInternal { pub updated_by: String, pub surcharge_applicable: Option, pub incremental_authorization_allowed: Option, + pub authorization_count: Option, } impl From for PaymentIntentUpdateInternal { @@ -381,6 +389,16 @@ impl From for PaymentIntentUpdateInternal { updated_by, ..Default::default() }, + PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } => Self { + amount: Some(amount), + ..Default::default() + }, + PaymentIntentUpdate::AuthorizationCountUpdate { + authorization_count, + } => Self { + authorization_count: Some(authorization_count), + ..Default::default() + }, } } } diff --git a/crates/diesel_models/src/authorization.rs b/crates/diesel_models/src/authorization.rs new file mode 100644 index 000000000000..64fd1c65187d --- /dev/null +++ b/crates/diesel_models/src/authorization.rs @@ -0,0 +1,78 @@ +use diesel::{AsChangeset, Identifiable, Insertable, Queryable}; +use serde::{Deserialize, Serialize}; +use time::PrimitiveDateTime; + +use crate::{enums as storage_enums, schema::incremental_authorization}; + +#[derive(Clone, Debug, Eq, PartialEq, Identifiable, Queryable, Serialize, Deserialize, Hash)] +#[diesel(table_name = incremental_authorization)] +#[diesel(primary_key(authorization_id, merchant_id))] +pub struct Authorization { + pub authorization_id: String, + pub merchant_id: String, + pub payment_id: String, + pub amount: i64, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub modified_at: PrimitiveDateTime, + pub status: storage_enums::AuthorizationStatus, + pub error_code: Option, + pub error_message: Option, + pub connector_authorization_id: Option, + pub previously_authorized_amount: i64, +} + +#[derive(Clone, Debug, Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize)] +#[diesel(table_name = incremental_authorization)] +pub struct AuthorizationNew { + pub authorization_id: String, + pub merchant_id: String, + pub payment_id: String, + pub amount: i64, + pub status: storage_enums::AuthorizationStatus, + pub error_code: Option, + pub error_message: Option, + pub connector_authorization_id: Option, + pub previously_authorized_amount: i64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum AuthorizationUpdate { + StatusUpdate { + status: storage_enums::AuthorizationStatus, + error_code: Option, + error_message: Option, + connector_authorization_id: Option, + }, +} + +#[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] +#[diesel(table_name = incremental_authorization)] +pub struct AuthorizationUpdateInternal { + pub status: Option, + pub error_code: Option, + pub error_message: Option, + pub modified_at: Option, + pub connector_authorization_id: Option, +} + +impl From for AuthorizationUpdateInternal { + fn from(authorization_child_update: AuthorizationUpdate) -> Self { + let now = Some(common_utils::date_time::now()); + match authorization_child_update { + AuthorizationUpdate::StatusUpdate { + status, + error_code, + error_message, + connector_authorization_id, + } => Self { + status: Some(status), + error_code, + error_message, + connector_authorization_id, + modified_at: now, + }, + } + } +} diff --git a/crates/diesel_models/src/enums.rs b/crates/diesel_models/src/enums.rs index 3f8b37cd03f7..17837d2ce5c7 100644 --- a/crates/diesel_models/src/enums.rs +++ b/crates/diesel_models/src/enums.rs @@ -452,10 +452,13 @@ pub enum DashboardMetadata { ConfiguredRouting, TestPayment, IntegrationMethod, + ConfigurationType, IntegrationCompleted, StripeConnected, PaypalConnected, SpRoutingConfigured, + Feedback, + ProdIntent, SpTestPayment, DownloadWoocom, ConfigureWoocom, diff --git a/crates/diesel_models/src/errors.rs b/crates/diesel_models/src/errors.rs index 0a8422131ae2..4a536aad07e4 100644 --- a/crates/diesel_models/src/errors.rs +++ b/crates/diesel_models/src/errors.rs @@ -1,4 +1,4 @@ -#[derive(Debug, thiserror::Error)] +#[derive(Copy, Clone, Debug, thiserror::Error)] pub enum DatabaseError { #[error("An error occurred when obtaining database connection")] DatabaseConnectionError, @@ -14,3 +14,17 @@ pub enum DatabaseError { #[error("An unknown error occurred")] Others, } + +impl From for DatabaseError { + fn from(error: diesel::result::Error) -> Self { + match error { + diesel::result::Error::DatabaseError( + diesel::result::DatabaseErrorKind::UniqueViolation, + _, + ) => Self::UniqueViolation, + diesel::result::Error::NotFound => Self::NotFound, + diesel::result::Error::QueryBuilderError(_) => Self::QueryGenerationFailed, + _ => Self::Others, + } + } +} diff --git a/crates/diesel_models/src/kv.rs b/crates/diesel_models/src/kv.rs index f56ef8304186..dd12a916c90f 100644 --- a/crates/diesel_models/src/kv.rs +++ b/crates/diesel_models/src/kv.rs @@ -31,6 +31,8 @@ impl TypedSql { request_id: String, global_id: String, ) -> crate::StorageResult> { + let pushed_at = common_utils::date_time::now_unix_timestamp(); + Ok(vec![ ( "typed_sql", @@ -40,6 +42,7 @@ impl TypedSql { ), ("global_id", global_id), ("request_id", request_id), + ("pushed_at", pushed_at.to_string()), ]) } } diff --git a/crates/diesel_models/src/lib.rs b/crates/diesel_models/src/lib.rs index 781099662a50..fa32fb84a15d 100644 --- a/crates/diesel_models/src/lib.rs +++ b/crates/diesel_models/src/lib.rs @@ -5,6 +5,7 @@ pub mod capture; pub mod cards_info; pub mod configs; +pub mod authorization; pub mod customers; pub mod dispute; pub mod encryption; diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index 216801fa8fb1..b1e8e144a9e3 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -269,6 +269,10 @@ pub enum PaymentAttemptUpdate { connector: Option, updated_by: String, }, + IncrementalAuthorizationAmountUpdate { + amount: i64, + amount_capturable: i64, + }, } #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] @@ -679,6 +683,14 @@ impl From for PaymentAttemptUpdateInternal { updated_by, ..Default::default() }, + PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { + amount, + amount_capturable, + } => Self { + amount: Some(amount), + amount_capturable: Some(amount_capturable), + ..Default::default() + }, } } } diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index 8d752466103e..1bd5c73a96ca 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -54,6 +54,7 @@ pub struct PaymentIntent { pub surcharge_applicable: Option, pub request_incremental_authorization: RequestIncrementalAuthorization, pub incremental_authorization_allowed: Option, + pub authorization_count: Option, } #[derive( @@ -106,11 +107,11 @@ pub struct PaymentIntentNew { pub merchant_decision: Option, pub payment_link_id: Option, pub payment_confirm_source: Option, - pub updated_by: String, pub surcharge_applicable: Option, pub request_incremental_authorization: RequestIncrementalAuthorization, pub incremental_authorization_allowed: Option, + pub authorization_count: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -188,6 +189,12 @@ pub enum PaymentIntentUpdate { surcharge_applicable: Option, updated_by: String, }, + IncrementalAuthorizationAmountUpdate { + amount: i64, + }, + AuthorizationCountUpdate { + authorization_count: i32, + }, } #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] @@ -221,6 +228,7 @@ pub struct PaymentIntentUpdateInternal { pub updated_by: String, pub surcharge_applicable: Option, pub incremental_authorization_allowed: Option, + pub authorization_count: Option, } impl PaymentIntentUpdate { @@ -252,6 +260,7 @@ impl PaymentIntentUpdate { updated_by, surcharge_applicable, incremental_authorization_allowed, + authorization_count, } = self.into(); PaymentIntent { amount: amount.unwrap_or(source.amount), @@ -283,6 +292,7 @@ impl PaymentIntentUpdate { surcharge_applicable: surcharge_applicable.or(source.surcharge_applicable), incremental_authorization_allowed, + authorization_count, ..source } } @@ -449,6 +459,16 @@ impl From for PaymentIntentUpdateInternal { updated_by, ..Default::default() }, + PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } => Self { + amount: Some(amount), + ..Default::default() + }, + PaymentIntentUpdate::AuthorizationCountUpdate { + authorization_count, + } => Self { + authorization_count: Some(authorization_count), + ..Default::default() + }, } } } diff --git a/crates/diesel_models/src/query.rs b/crates/diesel_models/src/query.rs index b0537d0a287b..3a3dee47a854 100644 --- a/crates/diesel_models/src/query.rs +++ b/crates/diesel_models/src/query.rs @@ -5,6 +5,7 @@ mod capture; pub mod cards_info; pub mod configs; +pub mod authorization; pub mod customers; pub mod dashboard_metadata; pub mod dispute; diff --git a/crates/diesel_models/src/query/authorization.rs b/crates/diesel_models/src/query/authorization.rs new file mode 100644 index 000000000000..dc9515bda55e --- /dev/null +++ b/crates/diesel_models/src/query/authorization.rs @@ -0,0 +1,79 @@ +use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods}; +use router_env::{instrument, tracing}; + +use super::generics; +use crate::{ + authorization::{ + Authorization, AuthorizationNew, AuthorizationUpdate, AuthorizationUpdateInternal, + }, + errors, + schema::incremental_authorization::dsl, + PgPooledConn, StorageResult, +}; + +impl AuthorizationNew { + #[instrument(skip(conn))] + pub async fn insert(self, conn: &PgPooledConn) -> StorageResult { + generics::generic_insert(conn, self).await + } +} + +impl Authorization { + #[instrument(skip(conn))] + pub async fn update_by_merchant_id_authorization_id( + conn: &PgPooledConn, + merchant_id: String, + authorization_id: String, + authorization_update: AuthorizationUpdate, + ) -> StorageResult { + match generics::generic_update_with_unique_predicate_get_result::< + ::Table, + _, + _, + _, + >( + conn, + dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(dsl::authorization_id.eq(authorization_id.to_owned())), + AuthorizationUpdateInternal::from(authorization_update), + ) + .await + { + Err(error) => match error.current_context() { + errors::DatabaseError::NotFound => Err(error.attach_printable( + "Authorization with the given Authorization ID does not exist", + )), + errors::DatabaseError::NoFieldsToUpdate => { + generics::generic_find_one::<::Table, _, _>( + conn, + dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(dsl::authorization_id.eq(authorization_id.to_owned())), + ) + .await + } + _ => Err(error), + }, + result => result, + } + } + + #[instrument(skip(conn))] + pub async fn find_by_merchant_id_payment_id( + conn: &PgPooledConn, + merchant_id: &str, + payment_id: &str, + ) -> StorageResult> { + generics::generic_filter::<::Table, _, _, _>( + conn, + dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(dsl::payment_id.eq(payment_id.to_owned())), + None, + None, + Some(dsl::created_at.asc()), + ) + .await + } +} diff --git a/crates/diesel_models/src/query/dashboard_metadata.rs b/crates/diesel_models/src/query/dashboard_metadata.rs index 44fd24c7acf2..678bcc2fd1f6 100644 --- a/crates/diesel_models/src/query/dashboard_metadata.rs +++ b/crates/diesel_models/src/query/dashboard_metadata.rs @@ -28,21 +28,36 @@ impl DashboardMetadata { data_key: enums::DashboardMetadata, dashboard_metadata_update: DashboardMetadataUpdate, ) -> StorageResult { - generics::generic_update_with_unique_predicate_get_result::< - ::Table, - _, - _, - _, - >( - conn, - dsl::user_id - .eq(user_id.to_owned()) - .and(dsl::merchant_id.eq(merchant_id.to_owned())) - .and(dsl::org_id.eq(org_id.to_owned())) - .and(dsl::data_key.eq(data_key.to_owned())), - DashboardMetadataUpdateInternal::from(dashboard_metadata_update), - ) - .await + let predicate = dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(dsl::org_id.eq(org_id.to_owned())) + .and(dsl::data_key.eq(data_key.to_owned())); + + if let Some(uid) = user_id { + generics::generic_update_with_unique_predicate_get_result::< + ::Table, + _, + _, + _, + >( + conn, + predicate.and(dsl::user_id.eq(uid)), + DashboardMetadataUpdateInternal::from(dashboard_metadata_update), + ) + .await + } else { + generics::generic_update_with_unique_predicate_get_result::< + ::Table, + _, + _, + _, + >( + conn, + predicate.and(dsl::user_id.is_null()), + DashboardMetadataUpdateInternal::from(dashboard_metadata_update), + ) + .await + } } pub async fn find_user_scoped_dashboard_metadata( diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 13b001ecc6d1..9baf613d9233 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -362,6 +362,31 @@ diesel::table! { } } +diesel::table! { + use diesel::sql_types::*; + use crate::enums::diesel_exports::*; + + incremental_authorization (authorization_id, merchant_id) { + #[max_length = 64] + authorization_id -> Varchar, + #[max_length = 64] + merchant_id -> Varchar, + #[max_length = 64] + payment_id -> Varchar, + amount -> Int8, + created_at -> Timestamp, + modified_at -> Timestamp, + #[max_length = 64] + status -> Varchar, + #[max_length = 255] + error_code -> Nullable, + error_message -> Nullable, + #[max_length = 64] + connector_authorization_id -> Nullable, + previously_authorized_amount -> Int8, + } +} + diesel::table! { use diesel::sql_types::*; use crate::enums::diesel_exports::*; @@ -680,6 +705,7 @@ diesel::table! { surcharge_applicable -> Nullable, request_incremental_authorization -> RequestIncrementalAuthorization, incremental_authorization_allowed -> Nullable, + authorization_count -> Nullable, } } @@ -997,6 +1023,7 @@ diesel::allow_tables_to_appear_in_same_query!( file_metadata, fraud_check, gateway_status_map, + incremental_authorization, locker_mock_up, mandate, merchant_account, diff --git a/crates/drainer/src/lib.rs b/crates/drainer/src/lib.rs index 94a29e3b0a04..796c9aa69550 100644 --- a/crates/drainer/src/lib.rs +++ b/crates/drainer/src/lib.rs @@ -7,7 +7,7 @@ pub mod settings; mod utils; use std::sync::{atomic, Arc}; -use common_utils::signals::get_allowed_signals; +use common_utils::{ext_traits::StringExt, signals::get_allowed_signals}; use diesel_models::kv; use error_stack::{IntoReport, ResultExt}; use router_env::{instrument, tracing}; @@ -199,15 +199,21 @@ async fn drainer( .get("request_id") .map_or(String::new(), Clone::clone); let global_id = entry.1.get("global_id").map_or(String::new(), Clone::clone); + let pushed_at = entry.1.get("pushed_at"); tracing::Span::current().record("request_id", request_id); tracing::Span::current().record("global_id", global_id); tracing::Span::current().record("session_id", &session_id); - let result = serde_json::from_str::(&typed_sql); + let result = typed_sql.parse_struct("DBOperation"); + let db_op = match result { Ok(f) => f, - Err(_err) => continue, // TODO: handle error + Err(err) => { + logger::error!(operation= "deserialization",error = %err); + metrics::STREAM_PARSE_FAIL.add(&metrics::CONTEXT, 1, &[]); + continue; + } }; let conn = pg_connection(&store.master_pool).await; @@ -261,6 +267,7 @@ async fn drainer( value: insert_op.into(), }], ); + utils::push_drainer_delay(pushed_at, insert_op.to_string()); } kv::DBOperation::Update { updatable } => { let (_, execution_time) = common_utils::date_time::time_it(|| async { @@ -302,6 +309,7 @@ async fn drainer( value: update_op.into(), }], ); + utils::push_drainer_delay(pushed_at, update_op.to_string()); } kv::DBOperation::Delete => { // [#224]: Implement this diff --git a/crates/drainer/src/metrics.rs b/crates/drainer/src/metrics.rs index 77f3d5e7db1d..750f23bc73b5 100644 --- a/crates/drainer/src/metrics.rs +++ b/crates/drainer/src/metrics.rs @@ -1,5 +1,7 @@ pub use router_env::opentelemetry::KeyValue; -use router_env::{counter_metric, global_meter, histogram_metric, metrics_context}; +use router_env::{ + counter_metric, global_meter, histogram_metric, histogram_metric_i64, metrics_context, +}; metrics_context!(CONTEXT); global_meter!(DRAINER_METER, "DRAINER"); @@ -12,9 +14,11 @@ counter_metric!(SUCCESSFUL_QUERY_EXECUTION, DRAINER_METER); counter_metric!(SHUTDOWN_SIGNAL_RECEIVED, DRAINER_METER); counter_metric!(SUCCESSFUL_SHUTDOWN, DRAINER_METER); counter_metric!(STREAM_EMPTY, DRAINER_METER); +counter_metric!(STREAM_PARSE_FAIL, DRAINER_METER); counter_metric!(DRAINER_HEALTH, DRAINER_METER); histogram_metric!(QUERY_EXECUTION_TIME, DRAINER_METER); // Time in (ms) milliseconds histogram_metric!(REDIS_STREAM_READ_TIME, DRAINER_METER); // Time in (ms) milliseconds histogram_metric!(REDIS_STREAM_TRIM_TIME, DRAINER_METER); // Time in (ms) milliseconds histogram_metric!(CLEANUP_TIME, DRAINER_METER); // Time in (ms) milliseconds +histogram_metric_i64!(DRAINER_DELAY_SECONDS, DRAINER_METER); // Time in (s) seconds diff --git a/crates/drainer/src/utils.rs b/crates/drainer/src/utils.rs index 2bd9f092f12c..5d3bd241d4df 100644 --- a/crates/drainer/src/utils.rs +++ b/crates/drainer/src/utils.rs @@ -128,6 +128,25 @@ pub fn parse_stream_entries<'a>( .into_report() } +pub fn push_drainer_delay(pushed_at: Option<&String>, operation: String) { + if let Some(pushed_at) = pushed_at { + if let Ok(time) = pushed_at.parse::() { + let drained_at = common_utils::date_time::now_unix_timestamp(); + let delay = drained_at - time; + + logger::debug!(operation = operation, delay = delay); + metrics::DRAINER_DELAY_SECONDS.record( + &metrics::CONTEXT, + delay, + &[metrics::KeyValue { + key: "operation".into(), + value: operation.into(), + }], + ); + } + } +} + // Here the output is in the format (stream_index, jobs_picked), // similar to the first argument of the function pub async fn increment_stream_index( diff --git a/crates/pm_auth/Cargo.toml b/crates/pm_auth/Cargo.toml new file mode 100644 index 000000000000..a9aebc5b540a --- /dev/null +++ b/crates/pm_auth/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "pm_auth" +description = "Open banking services" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +readme = "README.md" + +[dependencies] +# First party crates +api_models = { version = "0.1.0", path = "../api_models" } +common_enums = { version = "0.1.0", path = "../common_enums" } +common_utils = { version = "0.1.0", path = "../common_utils" } +masking = { version = "0.1.0", path = "../masking" } +router_derive = { version = "0.1.0", path = "../router_derive" } +router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra_implicit_fields", "log_custom_entries_to_extra"] } + +# Third party crates +async-trait = "0.1.66" +bytes = "1.4.0" +error-stack = "0.3.1" +http = "0.2.9" +mime = "0.3.17" +serde = "1.0.159" +serde_json = "1.0.91" +strum = { version = "0.24.1", features = ["derive"] } +thiserror = "1.0.43" diff --git a/crates/pm_auth/README.md b/crates/pm_auth/README.md new file mode 100644 index 000000000000..c630a7fe6761 --- /dev/null +++ b/crates/pm_auth/README.md @@ -0,0 +1,3 @@ +# Payment Method Auth Services + +An open banking services for payment method auth validation diff --git a/crates/pm_auth/src/connector.rs b/crates/pm_auth/src/connector.rs new file mode 100644 index 000000000000..56aad846e248 --- /dev/null +++ b/crates/pm_auth/src/connector.rs @@ -0,0 +1,3 @@ +pub mod plaid; + +pub use self::plaid::Plaid; diff --git a/crates/pm_auth/src/connector/plaid.rs b/crates/pm_auth/src/connector/plaid.rs new file mode 100644 index 000000000000..dfcfbb7eddcc --- /dev/null +++ b/crates/pm_auth/src/connector/plaid.rs @@ -0,0 +1,340 @@ +pub mod transformers; + +use std::fmt::Debug; + +use common_utils::{ + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, +}; +use error_stack::ResultExt; +use masking::{Mask, Maskable}; +use transformers as plaid; + +use crate::{ + core::errors, + types::{ + self as auth_types, + api::{ + auth_service::{self, BankAccountCredentials, ExchangeToken, LinkToken}, + ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, + }, + }, +}; + +#[derive(Debug, Clone)] +pub struct Plaid; + +impl ConnectorCommonExt for Plaid +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &auth_types::PaymentAuthRouterData, + _connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> errors::CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + "Content-Type".to_string(), + self.get_content_type().to_string().into(), + )]; + + let mut auth = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut auth); + Ok(header) + } +} + +impl ConnectorCommon for Plaid { + fn id(&self) -> &'static str { + "plaid" + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + fn base_url<'a>(&self, _connectors: &'a auth_types::PaymentMethodAuthConnectors) -> &'a str { + "https://sandbox.plaid.com" + } + + fn get_auth_header( + &self, + auth_type: &auth_types::ConnectorAuthType, + ) -> errors::CustomResult)>, errors::ConnectorError> { + let auth = plaid::PlaidAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let client_id = auth.client_id.into_masked(); + let secret = auth.secret.into_masked(); + + Ok(vec![ + ("PLAID-CLIENT-ID".to_string(), client_id), + ("PLAID-SECRET".to_string(), secret), + ]) + } + + fn build_error_response( + &self, + res: auth_types::Response, + ) -> errors::CustomResult { + let response: plaid::PlaidErrorResponse = + res.response + .parse_struct("PlaidErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + Ok(auth_types::ErrorResponse { + status_code: res.status_code, + code: crate::consts::NO_ERROR_CODE.to_string(), + message: response.error_message, + reason: response.display_message, + }) + } +} + +impl auth_service::AuthService for Plaid {} +impl auth_service::AuthServiceLinkToken for Plaid {} + +impl ConnectorIntegration + for Plaid +{ + fn get_headers( + &self, + req: &auth_types::LinkTokenRouterData, + connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> errors::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: &auth_types::LinkTokenRouterData, + connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> errors::CustomResult { + Ok(format!( + "{}{}", + self.base_url(connectors), + "/link/token/create" + )) + } + + fn get_request_body( + &self, + req: &auth_types::LinkTokenRouterData, + ) -> errors::CustomResult { + let req_obj = plaid::PlaidLinkTokenRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + + fn build_request( + &self, + req: &auth_types::LinkTokenRouterData, + connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> errors::CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&auth_types::PaymentAuthLinkTokenType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(auth_types::PaymentAuthLinkTokenType::get_headers( + self, req, connectors, + )?) + .set_body(auth_types::PaymentAuthLinkTokenType::get_request_body( + self, req, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &auth_types::LinkTokenRouterData, + res: auth_types::Response, + ) -> errors::CustomResult { + let response: plaid::PlaidLinkTokenResponse = res + .response + .parse_struct("PlaidLinkTokenResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + ::try_from(auth_types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + fn get_error_response( + &self, + res: auth_types::Response, + ) -> errors::CustomResult { + self.build_error_response(res) + } +} + +impl auth_service::AuthServiceExchangeToken for Plaid {} + +impl + ConnectorIntegration< + ExchangeToken, + auth_types::ExchangeTokenRequest, + auth_types::ExchangeTokenResponse, + > for Plaid +{ + fn get_headers( + &self, + req: &auth_types::ExchangeTokenRouterData, + connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> errors::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: &auth_types::ExchangeTokenRouterData, + connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> errors::CustomResult { + Ok(format!( + "{}{}", + self.base_url(connectors), + "/item/public_token/exchange" + )) + } + + fn get_request_body( + &self, + req: &auth_types::ExchangeTokenRouterData, + ) -> errors::CustomResult { + let req_obj = plaid::PlaidExchangeTokenRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + + fn build_request( + &self, + req: &auth_types::ExchangeTokenRouterData, + connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> errors::CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&auth_types::PaymentAuthExchangeTokenType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(auth_types::PaymentAuthExchangeTokenType::get_headers( + self, req, connectors, + )?) + .set_body(auth_types::PaymentAuthExchangeTokenType::get_request_body( + self, req, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &auth_types::ExchangeTokenRouterData, + res: auth_types::Response, + ) -> errors::CustomResult { + let response: plaid::PlaidExchangeTokenResponse = res + .response + .parse_struct("PlaidExchangeTokenResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + ::try_from(auth_types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + fn get_error_response( + &self, + res: auth_types::Response, + ) -> errors::CustomResult { + self.build_error_response(res) + } +} + +impl auth_service::AuthServiceBankAccountCredentials for Plaid {} + +impl + ConnectorIntegration< + BankAccountCredentials, + auth_types::BankAccountCredentialsRequest, + auth_types::BankAccountCredentialsResponse, + > for Plaid +{ + fn get_headers( + &self, + req: &auth_types::BankDetailsRouterData, + connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> errors::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: &auth_types::BankDetailsRouterData, + connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> errors::CustomResult { + Ok(format!("{}{}", self.base_url(connectors), "/auth/get")) + } + + fn get_request_body( + &self, + req: &auth_types::BankDetailsRouterData, + ) -> errors::CustomResult { + let req_obj = plaid::PlaidBankAccountCredentialsRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + + fn build_request( + &self, + req: &auth_types::BankDetailsRouterData, + connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> errors::CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&auth_types::PaymentAuthBankAccountDetailsType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(auth_types::PaymentAuthBankAccountDetailsType::get_headers( + self, req, connectors, + )?) + .set_body( + auth_types::PaymentAuthBankAccountDetailsType::get_request_body(self, req)?, + ) + .build(), + )) + } + + fn handle_response( + &self, + data: &auth_types::BankDetailsRouterData, + res: auth_types::Response, + ) -> errors::CustomResult { + let response: plaid::PlaidBankAccountCredentialsResponse = res + .response + .parse_struct("PlaidBankAccountCredentialsResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + ::try_from(auth_types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + fn get_error_response( + &self, + res: auth_types::Response, + ) -> errors::CustomResult { + self.build_error_response(res) + } +} diff --git a/crates/pm_auth/src/connector/plaid/transformers.rs b/crates/pm_auth/src/connector/plaid/transformers.rs new file mode 100644 index 000000000000..5e1ad67aead0 --- /dev/null +++ b/crates/pm_auth/src/connector/plaid/transformers.rs @@ -0,0 +1,294 @@ +use std::collections::HashMap; + +use common_enums::PaymentMethodType; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{core::errors, types}; + +#[derive(Debug, Serialize, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct PlaidLinkTokenRequest { + client_name: String, + country_codes: Vec, + language: String, + products: Vec, + user: User, +} + +#[derive(Debug, Serialize, Eq, PartialEq)] + +pub struct User { + pub client_user_id: String, +} + +impl TryFrom<&types::LinkTokenRouterData> for PlaidLinkTokenRequest { + type Error = error_stack::Report; + fn try_from(item: &types::LinkTokenRouterData) -> Result { + Ok(Self { + client_name: item.request.client_name.clone(), + country_codes: item.request.country_codes.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "country_codes", + }, + )?, + language: item.request.language.clone().unwrap_or("en".to_string()), + products: vec!["auth".to_string()], + user: User { + client_user_id: item.request.user_info.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "country_codes", + }, + )?, + }, + }) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct PlaidLinkTokenResponse { + link_token: String, +} + +impl + TryFrom> + for types::PaymentAuthRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(types::LinkTokenResponse { + link_token: item.response.link_token, + }), + ..item.data + }) + } +} + +#[derive(Debug, Serialize, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct PlaidExchangeTokenRequest { + public_token: String, +} + +#[derive(Debug, Deserialize, Eq, PartialEq)] + +pub struct PlaidExchangeTokenResponse { + pub access_token: String, +} + +impl + TryFrom< + types::ResponseRouterData, + > for types::PaymentAuthRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + PlaidExchangeTokenResponse, + T, + types::ExchangeTokenResponse, + >, + ) -> Result { + Ok(Self { + response: Ok(types::ExchangeTokenResponse { + access_token: item.response.access_token, + }), + ..item.data + }) + } +} + +impl TryFrom<&types::ExchangeTokenRouterData> for PlaidExchangeTokenRequest { + type Error = error_stack::Report; + fn try_from(item: &types::ExchangeTokenRouterData) -> Result { + Ok(Self { + public_token: item.request.public_token.clone(), + }) + } +} + +#[derive(Debug, Serialize, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct PlaidBankAccountCredentialsRequest { + access_token: String, + options: Option, +} + +#[derive(Debug, Deserialize, Eq, PartialEq)] + +pub struct PlaidBankAccountCredentialsResponse { + pub accounts: Vec, + pub numbers: PlaidBankAccountCredentialsNumbers, + // pub item: PlaidBankAccountCredentialsItem, + pub request_id: String, +} + +#[derive(Debug, Serialize, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct BankAccountCredentialsOptions { + account_ids: Vec, +} + +#[derive(Debug, Deserialize, Eq, PartialEq)] + +pub struct PlaidBankAccountCredentialsAccounts { + pub account_id: String, + pub name: String, + pub subtype: Option, +} + +#[derive(Debug, Deserialize, Eq, PartialEq)] +pub struct PlaidBankAccountCredentialsBalances { + pub available: Option, + pub current: Option, + pub limit: Option, + pub iso_currency_code: Option, + pub unofficial_currency_code: Option, + pub last_updated_datetime: Option, +} + +#[derive(Debug, Deserialize, Eq, PartialEq)] +pub struct PlaidBankAccountCredentialsNumbers { + pub ach: Vec, + pub eft: Vec, + pub international: Vec, + pub bacs: Vec, +} + +#[derive(Debug, Deserialize, Eq, PartialEq)] +pub struct PlaidBankAccountCredentialsItem { + pub item_id: String, + pub institution_id: Option, + pub webhook: Option, + pub error: Option, +} +#[derive(Debug, Deserialize, Eq, PartialEq)] +pub struct PlaidBankAccountCredentialsACH { + pub account_id: String, + pub account: String, + pub routing: String, + pub wire_routing: Option, +} + +#[derive(Debug, Deserialize, Eq, PartialEq)] +pub struct PlaidBankAccountCredentialsEFT { + pub account_id: String, + pub account: String, + pub institution: String, + pub branch: String, +} + +#[derive(Debug, Deserialize, Eq, PartialEq)] +pub struct PlaidBankAccountCredentialsInternational { + pub account_id: String, + pub iban: String, + pub bic: String, +} + +#[derive(Debug, Deserialize, Eq, PartialEq)] +pub struct PlaidBankAccountCredentialsBacs { + pub account_id: String, + pub account: String, + pub sort_code: String, +} + +impl TryFrom<&types::BankDetailsRouterData> for PlaidBankAccountCredentialsRequest { + type Error = error_stack::Report; + fn try_from(item: &types::BankDetailsRouterData) -> Result { + Ok(Self { + access_token: item.request.access_token.clone(), + options: item.request.optional_ids.as_ref().map(|bank_account_ids| { + BankAccountCredentialsOptions { + account_ids: bank_account_ids.ids.clone(), + } + }), + }) + } +} + +impl + TryFrom< + types::ResponseRouterData< + F, + PlaidBankAccountCredentialsResponse, + T, + types::BankAccountCredentialsResponse, + >, + > for types::PaymentAuthRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + PlaidBankAccountCredentialsResponse, + T, + types::BankAccountCredentialsResponse, + >, + ) -> Result { + let (account_numbers, accounts_info) = (item.response.numbers, item.response.accounts); + let mut bank_account_vec = Vec::new(); + let mut id_to_suptype = HashMap::new(); + + accounts_info.into_iter().for_each(|acc| { + id_to_suptype.insert(acc.account_id, (acc.subtype, acc.name)); + }); + + account_numbers.ach.into_iter().for_each(|ach| { + let (acc_type, acc_name) = + if let Some((_type, name)) = id_to_suptype.get(&ach.account_id) { + (_type.to_owned(), Some(name.clone())) + } else { + (None, None) + }; + + let bank_details_new = types::BankAccountDetails { + account_name: acc_name, + account_number: ach.account, + routing_number: ach.routing, + payment_method_type: PaymentMethodType::Ach, + account_id: ach.account_id, + account_type: acc_type, + }; + + bank_account_vec.push(bank_details_new); + }); + + Ok(Self { + response: Ok(types::BankAccountCredentialsResponse { + credentials: bank_account_vec, + }), + ..item.data + }) + } +} +pub struct PlaidAuthType { + pub client_id: Secret, + pub secret: Secret, +} + +impl TryFrom<&types::ConnectorAuthType> for PlaidAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + match auth_type { + types::ConnectorAuthType::BodyKey { client_id, secret } => Ok(Self { + client_id: client_id.to_owned(), + secret: secret.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub struct PlaidErrorResponse { + pub display_message: Option, + pub error_code: Option, + pub error_message: String, + pub error_type: Option, +} diff --git a/crates/pm_auth/src/consts.rs b/crates/pm_auth/src/consts.rs new file mode 100644 index 000000000000..dac3485ec8fc --- /dev/null +++ b/crates/pm_auth/src/consts.rs @@ -0,0 +1,5 @@ +pub const REQUEST_TIME_OUT: u64 = 30; // will timeout after the mentioned limit +pub const REQUEST_TIMEOUT_ERROR_CODE: &str = "TIMEOUT"; // timeout error code +pub const REQUEST_TIMEOUT_ERROR_MESSAGE: &str = "Connector did not respond in specified time"; // error message for timed out request +pub const NO_ERROR_CODE: &str = "No error code"; +pub const NO_ERROR_MESSAGE: &str = "No error message"; diff --git a/crates/pm_auth/src/core.rs b/crates/pm_auth/src/core.rs new file mode 100644 index 000000000000..629e98fbf874 --- /dev/null +++ b/crates/pm_auth/src/core.rs @@ -0,0 +1 @@ +pub mod errors; diff --git a/crates/pm_auth/src/core/errors.rs b/crates/pm_auth/src/core/errors.rs new file mode 100644 index 000000000000..31b178a6276f --- /dev/null +++ b/crates/pm_auth/src/core/errors.rs @@ -0,0 +1,27 @@ +#[derive(Debug, thiserror::Error, PartialEq)] +pub enum ConnectorError { + #[error("Failed to obtain authentication type")] + FailedToObtainAuthType, + #[error("Missing required field: {field_name}")] + MissingRequiredField { field_name: &'static str }, + #[error("Failed to execute a processing step: {0:?}")] + ProcessingStepFailed(Option), + #[error("Failed to deserialize connector response")] + ResponseDeserializationFailed, + #[error("Failed to encode connector request")] + RequestEncodingFailed, +} + +pub type CustomResult = error_stack::Result; + +#[derive(Debug, thiserror::Error)] +pub enum ParsingError { + #[error("Failed to parse enum: {0}")] + EnumParseFailure(&'static str), + #[error("Failed to parse struct: {0}")] + StructParseFailure(&'static str), + #[error("Failed to serialize to {0} format")] + EncodeError(&'static str), + #[error("Unknown error while parsing")] + UnknownError, +} diff --git a/crates/pm_auth/src/lib.rs b/crates/pm_auth/src/lib.rs new file mode 100644 index 000000000000..60d0e06a1e00 --- /dev/null +++ b/crates/pm_auth/src/lib.rs @@ -0,0 +1,4 @@ +pub mod connector; +pub mod consts; +pub mod core; +pub mod types; diff --git a/crates/pm_auth/src/types.rs b/crates/pm_auth/src/types.rs new file mode 100644 index 000000000000..6f5875247f1f --- /dev/null +++ b/crates/pm_auth/src/types.rs @@ -0,0 +1,152 @@ +pub mod api; + +use std::marker::PhantomData; + +use api::auth_service::{BankAccountCredentials, ExchangeToken, LinkToken}; +use common_enums::PaymentMethodType; +use masking::Secret; +#[derive(Debug, Clone)] +pub struct PaymentAuthRouterData { + pub flow: PhantomData, + pub merchant_id: Option, + pub connector: Option, + pub request: Request, + pub response: Result, + pub connector_auth_type: ConnectorAuthType, + pub connector_http_status_code: Option, +} + +#[derive(Debug, Clone)] +pub struct LinkTokenRequest { + pub client_name: String, + pub country_codes: Option>, + pub language: Option, + pub user_info: Option, +} + +#[derive(Debug, Clone)] +pub struct LinkTokenResponse { + pub link_token: String, +} + +pub type LinkTokenRouterData = + PaymentAuthRouterData; + +#[derive(Debug, Clone)] +pub struct ExchangeTokenRequest { + pub public_token: String, +} + +#[derive(Debug, Clone)] +pub struct ExchangeTokenResponse { + pub access_token: String, +} + +impl From for api_models::pm_auth::ExchangeTokenCreateResponse { + fn from(value: ExchangeTokenResponse) -> Self { + Self { + access_token: value.access_token, + } + } +} + +pub type ExchangeTokenRouterData = + PaymentAuthRouterData; + +#[derive(Debug, Clone)] +pub struct BankAccountCredentialsRequest { + pub access_token: String, + pub optional_ids: Option, +} + +#[derive(Debug, Clone)] +pub struct BankAccountOptionalIDs { + pub ids: Vec, +} + +#[derive(Debug, Clone)] +pub struct BankAccountCredentialsResponse { + pub credentials: Vec, +} + +#[derive(Debug, Clone)] +pub struct BankAccountDetails { + pub account_name: Option, + pub account_number: String, + pub routing_number: String, + pub payment_method_type: PaymentMethodType, + pub account_id: String, + pub account_type: Option, +} + +pub type BankDetailsRouterData = PaymentAuthRouterData< + BankAccountCredentials, + BankAccountCredentialsRequest, + BankAccountCredentialsResponse, +>; + +pub type PaymentAuthLinkTokenType = + dyn self::api::ConnectorIntegration; + +pub type PaymentAuthExchangeTokenType = + dyn self::api::ConnectorIntegration; + +pub type PaymentAuthBankAccountDetailsType = dyn self::api::ConnectorIntegration< + BankAccountCredentials, + BankAccountCredentialsRequest, + BankAccountCredentialsResponse, +>; + +#[derive(Clone, Debug, strum::EnumString, strum::Display)] +#[strum(serialize_all = "snake_case")] +pub enum PaymentMethodAuthConnectors { + Plaid, +} + +#[derive(Debug, Clone)] +pub struct ResponseRouterData { + pub response: R, + pub data: PaymentAuthRouterData, + pub http_code: u16, +} + +#[derive(Clone, Debug, serde::Serialize)] +pub struct ErrorResponse { + pub code: String, + pub message: String, + pub reason: Option, + pub status_code: u16, +} + +impl ErrorResponse { + fn get_not_implemented() -> Self { + Self { + code: "IR_00".to_string(), + message: "This API is under development and will be made available soon.".to_string(), + reason: None, + status_code: http::StatusCode::INTERNAL_SERVER_ERROR.as_u16(), + } + } +} + +#[derive(Default, Debug, Clone, serde::Deserialize)] +pub enum ConnectorAuthType { + BodyKey { + client_id: Secret, + secret: Secret, + }, + #[default] + NoKey, +} + +#[derive(Clone, Debug)] +pub struct Response { + pub headers: Option, + pub response: bytes::Bytes, + pub status_code: u16, +} + +#[derive(serde::Deserialize, Clone)] +pub struct AuthServiceQueryParam { + pub client_secret: Option, +} diff --git a/crates/pm_auth/src/types/api.rs b/crates/pm_auth/src/types/api.rs new file mode 100644 index 000000000000..159c1e816539 --- /dev/null +++ b/crates/pm_auth/src/types/api.rs @@ -0,0 +1,167 @@ +pub mod auth_service; + +use std::fmt::Debug; + +use common_utils::{ + errors::CustomResult, + request::{Request, RequestContent}, +}; +use masking::Maskable; + +use crate::{ + core::errors::ConnectorError, + types::{self as auth_types, api::auth_service::AuthService}, +}; + +#[async_trait::async_trait] +pub trait ConnectorIntegration: ConnectorIntegrationAny + Sync { + fn get_headers( + &self, + _req: &super::PaymentAuthRouterData, + _connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> CustomResult)>, ConnectorError> { + Ok(vec![]) + } + + fn get_content_type(&self) -> &'static str { + mime::APPLICATION_JSON.essence_str() + } + + fn get_url( + &self, + _req: &super::PaymentAuthRouterData, + _connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> CustomResult { + Ok(String::new()) + } + + fn get_request_body( + &self, + _req: &super::PaymentAuthRouterData, + ) -> CustomResult { + Ok(RequestContent::Json(Box::new(serde_json::json!(r#""#)))) + } + + fn build_request( + &self, + _req: &super::PaymentAuthRouterData, + _connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> CustomResult, ConnectorError> { + Ok(None) + } + + fn handle_response( + &self, + data: &super::PaymentAuthRouterData, + _res: auth_types::Response, + ) -> CustomResult, ConnectorError> + where + T: Clone, + Req: Clone, + Resp: Clone, + { + Ok(data.clone()) + } + + fn get_error_response( + &self, + _res: auth_types::Response, + ) -> CustomResult { + Ok(auth_types::ErrorResponse::get_not_implemented()) + } + + fn get_5xx_error_response( + &self, + res: auth_types::Response, + ) -> CustomResult { + let error_message = match res.status_code { + 500 => "internal_server_error", + 501 => "not_implemented", + 502 => "bad_gateway", + 503 => "service_unavailable", + 504 => "gateway_timeout", + 505 => "http_version_not_supported", + 506 => "variant_also_negotiates", + 507 => "insufficient_storage", + 508 => "loop_detected", + 510 => "not_extended", + 511 => "network_authentication_required", + _ => "unknown_error", + }; + Ok(auth_types::ErrorResponse { + code: res.status_code.to_string(), + message: error_message.to_string(), + reason: String::from_utf8(res.response.to_vec()).ok(), + status_code: res.status_code, + }) + } +} + +pub trait ConnectorCommonExt: + ConnectorCommon + ConnectorIntegration +{ + fn build_headers( + &self, + _req: &auth_types::PaymentAuthRouterData, + _connectors: &auth_types::PaymentMethodAuthConnectors, + ) -> CustomResult)>, ConnectorError> { + Ok(Vec::new()) + } +} + +pub type BoxedConnectorIntegration<'a, T, Req, Resp> = + Box<&'a (dyn ConnectorIntegration + Send + Sync)>; + +pub trait ConnectorIntegrationAny: Send + Sync + 'static { + fn get_connector_integration(&self) -> BoxedConnectorIntegration<'_, T, Req, Resp>; +} + +impl ConnectorIntegrationAny for S +where + S: ConnectorIntegration, +{ + fn get_connector_integration(&self) -> BoxedConnectorIntegration<'_, T, Req, Resp> { + Box::new(self) + } +} + +pub trait AuthServiceConnector: AuthService + Send + Debug {} + +impl AuthServiceConnector for T {} + +pub type BoxedPaymentAuthConnector = Box<&'static (dyn AuthServiceConnector + Sync)>; + +#[derive(Clone, Debug)] +pub struct PaymentAuthConnectorData { + pub connector: BoxedPaymentAuthConnector, + pub connector_name: super::PaymentMethodAuthConnectors, +} + +pub trait ConnectorCommon { + fn id(&self) -> &'static str; + + fn get_auth_header( + &self, + _auth_type: &auth_types::ConnectorAuthType, + ) -> CustomResult)>, ConnectorError> { + Ok(Vec::new()) + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a auth_types::PaymentMethodAuthConnectors) -> &'a str; + + fn build_error_response( + &self, + res: auth_types::Response, + ) -> CustomResult { + Ok(auth_types::ErrorResponse { + status_code: res.status_code, + code: crate::consts::NO_ERROR_CODE.to_string(), + message: crate::consts::NO_ERROR_MESSAGE.to_string(), + reason: None, + }) + } +} diff --git a/crates/pm_auth/src/types/api/auth_service.rs b/crates/pm_auth/src/types/api/auth_service.rs new file mode 100644 index 000000000000..35d44970d518 --- /dev/null +++ b/crates/pm_auth/src/types/api/auth_service.rs @@ -0,0 +1,40 @@ +use crate::types::{ + BankAccountCredentialsRequest, BankAccountCredentialsResponse, ExchangeTokenRequest, + ExchangeTokenResponse, LinkTokenRequest, LinkTokenResponse, +}; + +pub trait AuthService: + super::ConnectorCommon + + AuthServiceLinkToken + + AuthServiceExchangeToken + + AuthServiceBankAccountCredentials +{ +} + +#[derive(Debug, Clone)] +pub struct LinkToken; + +pub trait AuthServiceLinkToken: + super::ConnectorIntegration +{ +} + +#[derive(Debug, Clone)] +pub struct ExchangeToken; + +pub trait AuthServiceExchangeToken: + super::ConnectorIntegration +{ +} + +#[derive(Debug, Clone)] +pub struct BankAccountCredentials; + +pub trait AuthServiceBankAccountCredentials: + super::ConnectorIntegration< + BankAccountCredentials, + BankAccountCredentialsRequest, + BankAccountCredentialsResponse, +> +{ +} diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 2e70f2b98696..738f14bbe4a5 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -9,10 +9,11 @@ readme = "README.md" license.workspace = true [features] -default = ["kv_store", "stripe", "oltp", "olap", "backwards_compatibility", "accounts_cache", "dummy_connector", "payouts", "profile_specific_fallback_routing", "retry"] +default = ["kv_store", "stripe", "oltp", "olap", "backwards_compatibility", "accounts_cache", "dummy_connector", "payouts", "profile_specific_fallback_routing", "retry", "frm"] s3 = ["dep:aws-sdk-s3", "dep:aws-config"] kms = ["external_services/kms", "dep:aws-config"] email = ["external_services/email", "dep:aws-config", "olap"] +frm = [] basilisk = ["kms"] stripe = ["dep:serde_qs"] release = ["kms", "stripe", "basilisk", "s3", "email", "business_profile_routing", "accounts_cache", "kv_store", "profile_specific_fallback_routing"] @@ -110,6 +111,7 @@ currency_conversion = { version = "0.1.0", path = "../currency_conversion" } data_models = { version = "0.1.0", path = "../data_models", default-features = false } diesel_models = { version = "0.1.0", path = "../diesel_models", features = ["kv_store"] } euclid = { version = "0.1.0", path = "../euclid", features = ["valued_jit"] } +pm_auth = { version = "0.1.0", path = "../pm_auth", package = "pm_auth" } external_services = { version = "0.1.0", path = "../external_services" } kgraph_utils = { version = "0.1.0", path = "../kgraph_utils" } masking = { version = "0.1.0", path = "../masking" } diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index f9bfcae1ca10..744d7883e950 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -4106,6 +4106,58 @@ impl Default for super::settings::RequiredFields { ( enums::PaymentMethod::BankRedirect, PaymentMethodType(HashMap::from([ + ( + enums::PaymentMethodType::OpenBankingUk, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Volt, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap:: from([ + ( + "payment_method_data.bank_redirect.open_banking_uk.issuer".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.open_banking_uk.issuer".to_string(), + display_name: "issuer".to_string(), + field_type: enums::FieldType::UserBank, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ) + ]), + }, + ), ( enums::PaymentMethodType::Przelewy24, ConnectorFields { @@ -4125,13 +4177,85 @@ impl Default for super::settings::RequiredFields { ConnectorFields { fields: HashMap::from([ ( - enums::Connector::Stripe, + enums::Connector::Mollie, RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::new(), common: HashMap::new(), } ), + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "payment_method_data.bank_redirect.bancontact_card.billing_details.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.bancontact_card.billing_details.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "payment_method_data.bank_redirect.bancontact_card.billing_details.billing_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.bancontact_card.billing_details.billing_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ) + ]), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common:HashMap::from([ + ( + "payment_method_data.bank_redirect.bancontact_card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.bancontact_card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.bank_redirect.bancontact_card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.bancontact_card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.bank_redirect.bancontact_card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.bancontact_card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.bank_redirect.bancontact_card.card_holder_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.bancontact_card.card_holder_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ]), + } + ) ]), }, ), @@ -4241,24 +4365,6 @@ impl Default for super::settings::RequiredFields { mandate: HashMap::new(), non_mandate: HashMap::new(), common: HashMap::from([ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "billing.address.last_name".to_string(), - display_name: "billing_last_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), ( "email".to_string(), RequiredFieldInfo { @@ -4272,7 +4378,7 @@ impl Default for super::settings::RequiredFields { "billing.address.first_name".to_string(), RequiredFieldInfo { required_field: "billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), + display_name: "billing_first_name".to_string(), field_type: enums::FieldType::UserBillingName, value: None, } @@ -4281,7 +4387,7 @@ impl Default for super::settings::RequiredFields { "billing.address.last_name".to_string(), RequiredFieldInfo { required_field: "billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), + display_name: "billing_last_name".to_string(), field_type: enums::FieldType::UserBillingName, value: None, } @@ -4348,6 +4454,93 @@ impl Default for super::settings::RequiredFields { non_mandate: HashMap::new(), common: HashMap::new(), } + ), + ( + enums::Connector::Bankofamerica, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } ) ]), }, @@ -4450,7 +4643,7 @@ impl Default for super::settings::RequiredFields { ), common: HashMap::new(), } - ), + ) ]), }, ), diff --git a/crates/router/src/configs/kms.rs b/crates/router/src/configs/kms.rs index 37f2d15774a5..bf6ee44d28be 100644 --- a/crates/router/src/configs/kms.rs +++ b/crates/router/src/configs/kms.rs @@ -69,3 +69,36 @@ impl KmsDecrypt for settings::Database { }) } } + +#[cfg(feature = "olap")] +#[async_trait::async_trait] +impl KmsDecrypt for settings::PayPalOnboarding { + type Output = Self; + + async fn decrypt_inner( + mut self, + kms_client: &KmsClient, + ) -> CustomResult { + self.client_id = kms_client.decrypt(self.client_id.expose()).await?.into(); + self.client_secret = kms_client + .decrypt(self.client_secret.expose()) + .await? + .into(); + self.partner_id = kms_client.decrypt(self.partner_id.expose()).await?.into(); + Ok(self) + } +} + +#[cfg(feature = "olap")] +#[async_trait::async_trait] +impl KmsDecrypt for settings::ConnectorOnboarding { + type Output = Self; + + async fn decrypt_inner( + mut self, + kms_client: &KmsClient, + ) -> CustomResult { + self.paypal = self.paypal.decrypt_inner(kms_client).await?; + Ok(self) + } +} diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index f2d962b0abee..1c885e90cc75 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -100,6 +100,7 @@ pub struct Settings { pub required_fields: RequiredFields, pub delayed_session_response: DelayedSessionConfig, pub webhook_source_verification_call: WebhookSourceVerificationCall, + pub payment_method_auth: PaymentMethodAuth, pub connector_request_reference_id_config: ConnectorRequestReferenceIdConfig, #[cfg(feature = "payouts")] pub payouts: Payouts, @@ -113,9 +114,19 @@ pub struct Settings { pub analytics: AnalyticsConfig, #[cfg(feature = "kv_store")] pub kv_config: KvConfig, + #[cfg(feature = "frm")] + pub frm: Frm, #[cfg(feature = "olap")] pub report_download_config: ReportConfig, pub events: EventsConfig, + #[cfg(feature = "olap")] + pub connector_onboarding: ConnectorOnboarding, +} + +#[cfg(feature = "frm")] +#[derive(Debug, Deserialize, Clone, Default)] +pub struct Frm { + pub enabled: bool, } #[derive(Debug, Deserialize, Clone)] @@ -144,6 +155,12 @@ pub struct ForexApi { pub redis_lock_timeout: u64, } +#[derive(Debug, Deserialize, Clone, Default)] +pub struct PaymentMethodAuth { + pub redis_expiry: i64, + pub pm_auth_key: String, +} + #[derive(Debug, Deserialize, Clone, Default)] pub struct DefaultExchangeRates { pub base_currency: String, @@ -601,6 +618,7 @@ pub struct Connectors { pub prophetpay: ConnectorParams, pub rapyd: ConnectorParams, pub shift4: ConnectorParams, + pub signifyd: ConnectorParams, pub square: ConnectorParams, pub stax: ConnectorParams, pub stripe: ConnectorParamsWithFileUploadUrl, @@ -774,6 +792,7 @@ impl Settings { .list_separator(",") .with_list_parse_key("log.telemetry.route_to_trace") .with_list_parse_key("redis.cluster_urls") + .with_list_parse_key("events.kafka.brokers") .with_list_parse_key("connectors.supported.wallets") .with_list_parse_key("connector_request_reference_id_config.merchant_ids_send_payment_id_as_connector_request_id"), @@ -884,3 +903,18 @@ impl<'de> Deserialize<'de> for LockSettings { }) } } + +#[cfg(feature = "olap")] +#[derive(Debug, Deserialize, Clone, Default)] +pub struct ConnectorOnboarding { + pub paypal: PayPalOnboarding, +} + +#[cfg(feature = "olap")] +#[derive(Debug, Deserialize, Clone, Default)] +pub struct PayPalOnboarding { + pub client_id: masking::Secret, + pub client_secret: masking::Secret, + pub partner_id: masking::Secret, + pub enabled: bool, +} diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index 3a83fea0d910..55c61442591d 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -40,6 +40,7 @@ pub mod powertranz; pub mod prophetpay; pub mod rapyd; pub mod shift4; +pub mod signifyd; pub mod square; pub mod stax; pub mod stripe; @@ -63,7 +64,7 @@ pub use self::{ iatapay::Iatapay, klarna::Klarna, mollie::Mollie, multisafepay::Multisafepay, nexinets::Nexinets, nmi::Nmi, noon::Noon, nuvei::Nuvei, opayo::Opayo, opennode::Opennode, payeezy::Payeezy, payme::Payme, paypal::Paypal, payu::Payu, powertranz::Powertranz, - prophetpay::Prophetpay, rapyd::Rapyd, shift4::Shift4, square::Square, stax::Stax, - stripe::Stripe, trustpay::Trustpay, tsys::Tsys, volt::Volt, wise::Wise, worldline::Worldline, - worldpay::Worldpay, zen::Zen, + prophetpay::Prophetpay, rapyd::Rapyd, shift4::Shift4, signifyd::Signifyd, square::Square, + stax::Stax, stripe::Stripe, trustpay::Trustpay, tsys::Tsys, volt::Volt, wise::Wise, + worldline::Worldline, worldpay::Worldpay, zen::Zen, }; diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index e31a69669c6d..bbec9022835c 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -6,8 +6,8 @@ use serde::{Deserialize, Serialize}; use crate::{ connector::utils::{ - self, AddressDetailsData, CardData, CardIssuer, PaymentsAuthorizeRequestData, - PaymentsSyncRequestData, RouterData, + self, AddressDetailsData, ApplePayDecrypt, CardData, CardIssuer, + PaymentsAuthorizeRequestData, PaymentsSyncRequestData, RouterData, }, consts, core::errors, @@ -16,6 +16,7 @@ use crate::{ api::{self, enums as api_enums}, storage::enums, transformers::ForeignFrom, + ApplePayPredecryptData, }, }; @@ -110,11 +111,18 @@ pub struct GooglePayPaymentInformation { fluid_data: FluidData, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplePayPaymentInformation { + tokenized_card: TokenizedCard, +} + #[derive(Debug, Serialize)] #[serde(untagged)] pub enum PaymentInformation { Cards(CardPaymentInformation), GooglePay(GooglePayPaymentInformation), + ApplePay(ApplePayPaymentInformation), } #[derive(Debug, Serialize)] @@ -128,6 +136,16 @@ pub struct Card { card_type: Option, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TokenizedCard { + number: Secret, + expiration_month: Secret, + expiration_year: Secret, + cryptogram: Secret, + transaction_type: TransactionType, +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct FluidData { @@ -215,6 +233,12 @@ impl From for String { } } +#[derive(Debug, Serialize)] +pub enum TransactionType { + #[serde(rename = "1")] + ApplePay, +} + impl From<( &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, @@ -320,6 +344,48 @@ impl } } +impl + TryFrom<( + &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + Box, + )> for BankOfAmericaPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, apple_pay_data): ( + &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, + Box, + ), + ) -> Result { + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; + let order_information = OrderInformationWithBill::from((item, bill_to)); + let processing_information = + ProcessingInformation::from((item, Some(PaymentSolution::ApplePay))); + let client_reference_information = ClientReferenceInformation::from(item); + + let expiration_month = apple_pay_data.get_expiry_month()?; + let expiration_year = apple_pay_data.get_four_digit_expiry_year()?; + + let payment_information = PaymentInformation::ApplePay(ApplePayPaymentInformation { + tokenized_card: TokenizedCard { + number: apple_pay_data.application_primary_account_number, + cryptogram: apple_pay_data.payment_data.online_payment_cryptogram, + transaction_type: TransactionType::ApplePay, + expiration_year, + expiration_month, + }, + }); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + }) + } +} + impl TryFrom<( &BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>, @@ -368,6 +434,17 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> match item.router_data.request.payment_method_data.clone() { payments::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), payments::PaymentMethodData::Wallet(wallet_data) => match wallet_data { + payments::WalletData::ApplePay(_) => { + let payment_method_token = item.router_data.get_payment_method_token()?; + match payment_method_token { + types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { + Self::try_from((item, decrypt_data)) + } + types::PaymentMethodToken::Token(_) => { + Err(errors::ConnectorError::InvalidWalletToken)? + } + } + } payments::WalletData::GooglePay(google_pay_data) => { Self::try_from((item, google_pay_data)) } @@ -378,7 +455,6 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> | payments::WalletData::KakaoPayRedirect(_) | payments::WalletData::GoPayRedirect(_) | payments::WalletData::GcashRedirect(_) - | payments::WalletData::ApplePay(_) | payments::WalletData::ApplePayRedirect(_) | payments::WalletData::ApplePayThirdPartySdk(_) | payments::WalletData::DanaRedirect {} diff --git a/crates/router/src/connector/braintree/transformers.rs b/crates/router/src/connector/braintree/transformers.rs index 44daef94e8a6..f4bd62add3b9 100644 --- a/crates/router/src/connector/braintree/transformers.rs +++ b/crates/router/src/connector/braintree/transformers.rs @@ -228,17 +228,16 @@ impl types::PaymentsResponseData, >, ) -> Result { + let id = item.response.transaction.id.clone(); Ok(Self { status: enums::AttemptStatus::from(item.response.transaction.status), response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.transaction.id, - ), + resource_id: types::ResponseId::ConnectorTransactionId(id.clone()), redirection_data: None, mandate_reference: None, connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some(id), incremental_authorization_allowed: None, }), ..item.data diff --git a/crates/router/src/connector/checkout/transformers.rs b/crates/router/src/connector/checkout/transformers.rs index ebe02f30d5ff..37c038c22afe 100644 --- a/crates/router/src/connector/checkout/transformers.rs +++ b/crates/router/src/connector/checkout/transformers.rs @@ -1,12 +1,12 @@ use common_utils::{errors::CustomResult, ext_traits::ByteSliceExt}; use error_stack::{IntoReport, ResultExt}; -use masking::{ExposeInterface, PeekInterface, Secret}; +use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; use url::Url; use crate::{ - connector::utils::{self, PaymentsCaptureRequestData, RouterData, WalletData}, + connector::utils::{self, ApplePayDecrypt, PaymentsCaptureRequestData, RouterData, WalletData}, consts, core::errors, services, @@ -304,24 +304,8 @@ impl TryFrom<&CheckoutRouterData<&types::PaymentsAuthorizeRouterData>> for Payme })) } types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { - let expiry_year_4_digit = Secret::new(format!( - "20{}", - decrypt_data - .clone() - .application_expiration_date - .peek() - .get(0..2) - .ok_or(errors::ConnectorError::RequestEncodingFailed)? - )); - let exp_month = Secret::new( - decrypt_data - .clone() - .application_expiration_date - .peek() - .get(2..4) - .ok_or(errors::ConnectorError::RequestEncodingFailed)? - .to_owned(), - ); + let exp_month = decrypt_data.get_expiry_month()?; + let expiry_year_4_digit = decrypt_data.get_four_digit_expiry_year()?; Ok(PaymentSource::ApplePayPredecrypt(Box::new( ApplePayPredecrypt { token: decrypt_data.application_primary_account_number, diff --git a/crates/router/src/connector/cybersource.rs b/crates/router/src/connector/cybersource.rs index 0d9db0393c77..e7b68f759a96 100644 --- a/crates/router/src/connector/cybersource.rs +++ b/crates/router/src/connector/cybersource.rs @@ -216,7 +216,10 @@ where ("Host".to_string(), host.to_string().into()), ("Signature".to_string(), signature.into_masked()), ]; - if matches!(http_method, services::Method::Post | services::Method::Put) { + if matches!( + http_method, + services::Method::Post | services::Method::Put | services::Method::Patch + ) { headers.push(( "Digest".to_string(), format!("SHA-256={sha256}").into_masked(), @@ -231,6 +234,7 @@ impl api::PaymentAuthorize for Cybersource {} impl api::PaymentSync for Cybersource {} impl api::PaymentVoid for Cybersource {} impl api::PaymentCapture for Cybersource {} +impl api::PaymentIncrementalAuthorization for Cybersource {} impl api::MandateSetup for Cybersource {} impl api::ConnectorAccessToken for Cybersource {} impl api::PaymentToken for Cybersource {} @@ -845,6 +849,110 @@ impl ConnectorIntegration for Cybersource +{ + fn get_headers( + &self, + req: &types::PaymentsIncrementalAuthorizationRouterData, + 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::PaymentsIncrementalAuthorizationRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}pts/v2/payments/{}", + self.base_url(connectors), + connector_payment_id + )) + } + + fn get_request_body( + &self, + req: &types::PaymentsIncrementalAuthorizationRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = cybersource::CybersourceRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.additional_amount, + req, + ))?; + let connector_request = + cybersource::CybersourcePaymentsIncrementalAuthorizationRequest::try_from( + &connector_router_data, + )?; + Ok(RequestContent::Json(Box::new(connector_request))) + } + fn build_request( + &self, + req: &types::PaymentsIncrementalAuthorizationRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Patch) + .url(&types::IncrementalAuthorizationType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::IncrementalAuthorizationType::get_headers( + self, req, connectors, + )?) + .set_body(types::IncrementalAuthorizationType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + fn handle_response( + &self, + data: &types::PaymentsIncrementalAuthorizationRouterData, + res: types::Response, + ) -> CustomResult< + types::RouterData< + api::IncrementalAuthorization, + types::PaymentsIncrementalAuthorizationData, + types::PaymentsResponseData, + >, + errors::ConnectorError, + > { + let response: cybersource::CybersourcePaymentsIncrementalAuthorizationResponse = res + .response + .parse_struct("Cybersource PaymentResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(( + types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }, + true, + )) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } + fn get_error_response( + &self, + res: types::Response, + ) -> CustomResult { + self.build_error_response(res) + } +} + #[async_trait::async_trait] impl api::IncomingWebhook for Cybersource { fn get_webhook_object_reference_id( diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs index 953f82c76a83..d3f542d2013a 100644 --- a/crates/router/src/connector/cybersource/transformers.rs +++ b/crates/router/src/connector/cybersource/transformers.rs @@ -77,9 +77,11 @@ impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest { Some(vec![CybersourceActionsTokenType::InstrumentIdentifier]), Some(CybersourceAuthorizationOptions { initiator: CybersourcePaymentInitiator { - initiator_type: CybersourcePaymentInitiatorTypes::Customer, - credential_stored_on_file: true, + initiator_type: Some(CybersourcePaymentInitiatorTypes::Customer), + credential_stored_on_file: Some(true), + stored_credential_used: None, }, + merchant_intitiated_transaction: None, }), ); @@ -158,14 +160,22 @@ pub enum CybersourceActionsTokenType { #[serde(rename_all = "camelCase")] pub struct CybersourceAuthorizationOptions { initiator: CybersourcePaymentInitiator, + merchant_intitiated_transaction: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MerchantInitiatedTransaction { + reason: String, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct CybersourcePaymentInitiator { #[serde(rename = "type")] - initiator_type: CybersourcePaymentInitiatorTypes, - credential_stored_on_file: bool, + initiator_type: Option, + credential_stored_on_file: Option, + stored_credential_used: Option, } #[derive(Debug, Serialize)] @@ -229,6 +239,12 @@ pub struct OrderInformationWithBill { bill_to: Option, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct OrderInformationIncrementalAuthorization { + amount_details: AdditionalAmount, +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct OrderInformation { @@ -242,6 +258,13 @@ pub struct Amount { currency: String, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AdditionalAmount { + additional_amount: String, + currency: String, +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct BillTo { @@ -305,9 +328,11 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> Some(vec![CybersourceActionsTokenType::InstrumentIdentifier]), Some(CybersourceAuthorizationOptions { initiator: CybersourcePaymentInitiator { - initiator_type: CybersourcePaymentInitiatorTypes::Customer, - credential_stored_on_file: true, + initiator_type: Some(CybersourcePaymentInitiatorTypes::Customer), + credential_stored_on_file: Some(true), + stored_credential_used: None, }, + merchant_intitiated_transaction: None, }), ) } else { @@ -390,6 +415,13 @@ pub struct CybersourcePaymentsCaptureRequest { order_information: OrderInformationWithBill, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourcePaymentsIncrementalAuthorizationRequest { + processing_information: ProcessingInformation, + order_information: OrderInformationIncrementalAuthorization, +} + impl TryFrom<&CybersourceRouterData<&types::PaymentsCaptureRouterData>> for CybersourcePaymentsCaptureRequest { @@ -420,6 +452,41 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsCaptureRouterData>> } } +impl TryFrom<&CybersourceRouterData<&types::PaymentsIncrementalAuthorizationRouterData>> + for CybersourcePaymentsIncrementalAuthorizationRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &CybersourceRouterData<&types::PaymentsIncrementalAuthorizationRouterData>, + ) -> Result { + Ok(Self { + processing_information: ProcessingInformation { + action_list: None, + action_token_types: None, + authorization_options: Some(CybersourceAuthorizationOptions { + initiator: CybersourcePaymentInitiator { + initiator_type: None, + credential_stored_on_file: None, + stored_credential_used: Some(true), + }, + merchant_intitiated_transaction: Some(MerchantInitiatedTransaction { + reason: "5".to_owned(), + }), + }), + commerce_indicator: CybersourceCommerceIndicator::Internet, + capture: None, + capture_options: None, + }, + order_information: OrderInformationIncrementalAuthorization { + amount_details: AdditionalAmount { + additional_amount: item.amount.clone(), + currency: item.router_data.request.currency.to_string(), + }, + }, + }) + } +} + pub struct CybersourceAuthType { pub(super) api_key: Secret, pub(super) merchant_account: Secret, @@ -461,6 +528,14 @@ pub enum CybersourcePaymentStatus { Processing, } +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum CybersourceIncrementalAuthorizationStatus { + Authorized, + Declined, + AuthorizedPendingReview, +} + impl From for enums::AttemptStatus { fn from(item: CybersourcePaymentStatus) -> Self { match item { @@ -477,6 +552,16 @@ impl From for enums::AttemptStatus { } } +impl From for common_enums::AuthorizationStatus { + fn from(item: CybersourceIncrementalAuthorizationStatus) -> Self { + match item { + CybersourceIncrementalAuthorizationStatus::Authorized + | CybersourceIncrementalAuthorizationStatus::AuthorizedPendingReview => Self::Success, + CybersourceIncrementalAuthorizationStatus::Declined => Self::Failure, + } + } +} + impl From for enums::RefundStatus { fn from(item: CybersourcePaymentStatus) -> Self { match item { @@ -499,6 +584,13 @@ pub struct CybersourcePaymentsResponse { token_information: Option, } +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CybersourcePaymentsIncrementalAuthorizationResponse { + status: CybersourceIncrementalAuthorizationStatus, + error_information: Option, +} + #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CybersourceSetupMandatesResponse { @@ -654,6 +746,54 @@ impl } } +impl + TryFrom<( + types::ResponseRouterData< + F, + CybersourcePaymentsIncrementalAuthorizationResponse, + T, + types::PaymentsResponseData, + >, + bool, + )> for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + data: ( + types::ResponseRouterData< + F, + CybersourcePaymentsIncrementalAuthorizationResponse, + T, + types::PaymentsResponseData, + >, + bool, + ), + ) -> Result { + let item = data.0; + Ok(Self { + response: match item.response.error_information { + Some(error) => Ok( + types::PaymentsResponseData::IncrementalAuthorizationResponse { + status: common_enums::AuthorizationStatus::Failure, + error_code: Some(error.reason), + error_message: Some(error.message), + connector_authorization_id: None, + }, + ), + _ => Ok( + types::PaymentsResponseData::IncrementalAuthorizationResponse { + status: item.response.status.into(), + error_code: None, + error_message: None, + connector_authorization_id: None, + }, + ), + }, + ..item.data + }) + } +} + #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CybersourceTransactionResponse { diff --git a/crates/router/src/connector/signifyd.rs b/crates/router/src/connector/signifyd.rs new file mode 100644 index 000000000000..90ed3143d0e4 --- /dev/null +++ b/crates/router/src/connector/signifyd.rs @@ -0,0 +1,624 @@ +pub mod transformers; +use std::fmt::Debug; + +use common_utils::request::RequestContent; +use error_stack::{IntoReport, ResultExt}; +use masking::PeekInterface; +use transformers as signifyd; + +use crate::{ + configs::settings, + core::errors::{self, CustomResult}, + headers, + services::{request, ConnectorIntegration, ConnectorValidation}, + types::{ + self, + api::{self, ConnectorCommon, ConnectorCommonExt}, + }, +}; +#[cfg(feature = "frm")] +use crate::{ + services, + types::{api::fraud_check as frm_api, fraud_check as frm_types, ErrorResponse, Response}, + utils::BytesExt, +}; + +#[derive(Debug, Clone)] +pub struct Signifyd; + +impl ConnectorCommonExt for Signifyd +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &types::RouterData, + _connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Signifyd { + fn id(&self) -> &'static str { + "signifyd" + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + connectors.signifyd.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &types::ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = signifyd::SignifydAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let auth_api_key = format!("Basic {}", auth.api_key.peek()); + + Ok(vec![( + headers::AUTHORIZATION.to_string(), + request::Mask::into_masked(auth_api_key), + )]) + } + + #[cfg(feature = "frm")] + fn build_error_response( + &self, + res: Response, + ) -> CustomResult { + let response: signifyd::SignifydErrorResponse = res + .response + .parse_struct("SignifydErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + Ok(ErrorResponse { + status_code: res.status_code, + code: crate::consts::NO_ERROR_CODE.to_string(), + message: response.messages.join(" &"), + reason: Some(response.errors.to_string()), + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl api::Payment for Signifyd {} +impl api::PaymentAuthorize for Signifyd {} +impl api::PaymentSync for Signifyd {} +impl api::PaymentVoid for Signifyd {} +impl api::PaymentCapture for Signifyd {} +impl api::MandateSetup for Signifyd {} +impl api::ConnectorAccessToken for Signifyd {} +impl api::PaymentToken for Signifyd {} +impl api::Refund for Signifyd {} +impl api::RefundExecute for Signifyd {} +impl api::RefundSync for Signifyd {} +impl ConnectorValidation for Signifyd {} + +impl + ConnectorIntegration< + api::PaymentMethodToken, + types::PaymentMethodTokenizationData, + types::PaymentsResponseData, + > for Signifyd +{ +} + +impl ConnectorIntegration + for Signifyd +{ +} + +impl + ConnectorIntegration< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + > for Signifyd +{ +} + +impl api::PaymentSession for Signifyd {} + +impl ConnectorIntegration + for Signifyd +{ +} + +impl ConnectorIntegration + for Signifyd +{ +} + +impl ConnectorIntegration + for Signifyd +{ +} + +impl ConnectorIntegration + for Signifyd +{ +} + +impl ConnectorIntegration + for Signifyd +{ +} + +impl ConnectorIntegration + for Signifyd +{ +} + +impl ConnectorIntegration for Signifyd {} + +#[cfg(feature = "frm")] +impl api::FraudCheck for Signifyd {} +#[cfg(feature = "frm")] +impl frm_api::FraudCheckSale for Signifyd {} +#[cfg(feature = "frm")] +impl frm_api::FraudCheckCheckout for Signifyd {} +#[cfg(feature = "frm")] +impl frm_api::FraudCheckTransaction for Signifyd {} +#[cfg(feature = "frm")] +impl frm_api::FraudCheckFulfillment for Signifyd {} +#[cfg(feature = "frm")] +impl frm_api::FraudCheckRecordReturn for Signifyd {} + +#[cfg(feature = "frm")] +impl + ConnectorIntegration< + frm_api::Sale, + frm_types::FraudCheckSaleData, + frm_types::FraudCheckResponseData, + > for Signifyd +{ + fn get_headers( + &self, + req: &frm_types::FrmSaleRouterData, + 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: &frm_types::FrmSaleRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!( + "{}{}", + self.base_url(connectors), + "/v3/orders/events/sales" + )) + } + + fn get_request_body( + &self, + req: &frm_types::FrmSaleRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let req_obj = signifyd::SignifydPaymentsSaleRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + + fn build_request( + &self, + req: &frm_types::FrmSaleRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&frm_types::FrmSaleType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(frm_types::FrmSaleType::get_headers(self, req, connectors)?) + .set_body(frm_types::FrmSaleType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &frm_types::FrmSaleRouterData, + res: Response, + ) -> CustomResult { + let response: signifyd::SignifydPaymentsResponse = res + .response + .parse_struct("SignifydPaymentsResponse Sale") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + ::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) + } +} + +#[cfg(feature = "frm")] +impl + ConnectorIntegration< + frm_api::Checkout, + frm_types::FraudCheckCheckoutData, + frm_types::FraudCheckResponseData, + > for Signifyd +{ + fn get_headers( + &self, + req: &frm_types::FrmCheckoutRouterData, + 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: &frm_types::FrmCheckoutRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!( + "{}{}", + self.base_url(connectors), + "/v3/orders/events/checkouts" + )) + } + + fn get_request_body( + &self, + req: &frm_types::FrmCheckoutRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let req_obj = signifyd::SignifydPaymentsCheckoutRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + + fn build_request( + &self, + req: &frm_types::FrmCheckoutRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&frm_types::FrmCheckoutType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(frm_types::FrmCheckoutType::get_headers( + self, req, connectors, + )?) + .set_body(frm_types::FrmCheckoutType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &frm_types::FrmCheckoutRouterData, + res: Response, + ) -> CustomResult { + let response: signifyd::SignifydPaymentsResponse = res + .response + .parse_struct("SignifydPaymentsResponse Checkout") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + ::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) + } +} + +#[cfg(feature = "frm")] +impl + ConnectorIntegration< + frm_api::Transaction, + frm_types::FraudCheckTransactionData, + frm_types::FraudCheckResponseData, + > for Signifyd +{ + fn get_headers( + &self, + req: &frm_types::FrmTransactionRouterData, + 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: &frm_types::FrmTransactionRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!( + "{}{}", + self.base_url(connectors), + "/v3/orders/events/transactions" + )) + } + + fn get_request_body( + &self, + req: &frm_types::FrmTransactionRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let req_obj = signifyd::SignifydPaymentsTransactionRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + + fn build_request( + &self, + req: &frm_types::FrmTransactionRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&frm_types::FrmTransactionType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(frm_types::FrmTransactionType::get_headers( + self, req, connectors, + )?) + .set_body(frm_types::FrmTransactionType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &frm_types::FrmTransactionRouterData, + res: Response, + ) -> CustomResult { + let response: signifyd::SignifydPaymentsResponse = res + .response + .parse_struct("SignifydPaymentsResponse Transaction") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + ::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) + } +} + +#[cfg(feature = "frm")] +impl + ConnectorIntegration< + frm_api::Fulfillment, + frm_types::FraudCheckFulfillmentData, + frm_types::FraudCheckResponseData, + > for Signifyd +{ + fn get_headers( + &self, + req: &frm_types::FrmFulfillmentRouterData, + 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: &frm_types::FrmFulfillmentRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!( + "{}{}", + self.base_url(connectors), + "/v3/orders/events/fulfillments" + )) + } + + fn get_request_body( + &self, + req: &frm_types::FrmFulfillmentRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let req_obj = &req.request.fulfillment_request; + Ok(RequestContent::Json(Box::new(req_obj.clone()))) + } + + fn build_request( + &self, + req: &frm_types::FrmFulfillmentRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&frm_types::FrmFulfillmentType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(frm_types::FrmFulfillmentType::get_headers( + self, req, connectors, + )?) + .set_body(frm_types::FrmFulfillmentType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &frm_types::FrmFulfillmentRouterData, + res: Response, + ) -> CustomResult { + let response: signifyd::FrmFullfillmentSignifydApiResponse = res + .response + .parse_struct("FrmFullfillmentSignifydApiResponse Sale") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + frm_types::FrmFulfillmentRouterData::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) + } +} + +#[cfg(feature = "frm")] +impl + ConnectorIntegration< + frm_api::RecordReturn, + frm_types::FraudCheckRecordReturnData, + frm_types::FraudCheckResponseData, + > for Signifyd +{ + fn get_headers( + &self, + req: &frm_types::FrmRecordReturnRouterData, + 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: &frm_types::FrmRecordReturnRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!( + "{}{}", + self.base_url(connectors), + "/v3/orders/events/returns/records" + )) + } + + fn get_request_body( + &self, + req: &frm_types::FrmRecordReturnRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let req_obj = signifyd::SignifydPaymentsRecordReturnRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + + fn build_request( + &self, + req: &frm_types::FrmRecordReturnRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&frm_types::FrmRecordReturnType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(frm_types::FrmRecordReturnType::get_headers( + self, req, connectors, + )?) + .set_body(frm_types::FrmRecordReturnType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &frm_types::FrmRecordReturnRouterData, + res: Response, + ) -> CustomResult { + let response: signifyd::SignifydPaymentsRecordReturnResponse = res + .response + .parse_struct("SignifydPaymentsResponse Transaction") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + ::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) + } +} + +#[async_trait::async_trait] +impl api::IncomingWebhook for Signifyd { + fn get_webhook_object_reference_id( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + } + + fn get_webhook_event_type( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + } + + fn get_webhook_resource_object( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::WebhooksNotImplemented).into_report() + } +} diff --git a/crates/router/src/connector/signifyd/transformers.rs b/crates/router/src/connector/signifyd/transformers.rs new file mode 100644 index 000000000000..4f155f341f6d --- /dev/null +++ b/crates/router/src/connector/signifyd/transformers.rs @@ -0,0 +1,7 @@ +#[cfg(feature = "frm")] +pub mod api; +pub mod auth; + +#[cfg(feature = "frm")] +pub use self::api::*; +pub use self::auth::*; diff --git a/crates/router/src/connector/signifyd/transformers/api.rs b/crates/router/src/connector/signifyd/transformers/api.rs new file mode 100644 index 000000000000..1a1b09bd2880 --- /dev/null +++ b/crates/router/src/connector/signifyd/transformers/api.rs @@ -0,0 +1,589 @@ +use bigdecimal::ToPrimitive; +use common_utils::pii::Email; +use error_stack; +use masking::Secret; +use serde::{Deserialize, Serialize}; +use time::PrimitiveDateTime; +use utoipa::ToSchema; + +use crate::{ + connector::utils::{ + AddressDetailsData, FraudCheckCheckoutRequest, FraudCheckRecordReturnRequest, + FraudCheckSaleRequest, FraudCheckTransactionRequest, RouterData, + }, + core::{ + errors, + fraud_check::types::{self as core_types, FrmFulfillmentRequest}, + }, + types::{ + self, api::Fulfillment, fraud_check as frm_types, storage::enums as storage_enums, + ResponseId, ResponseRouterData, + }, +}; + +#[allow(dead_code)] +#[derive(Debug, Serialize, Eq, PartialEq, Deserialize, Clone)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum DecisionDelivery { + Sync, + AsyncOnly, +} + +#[derive(Debug, Serialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Purchase { + #[serde(with = "common_utils::custom_serde::iso8601")] + created_at: PrimitiveDateTime, + order_channel: OrderChannel, + total_price: i64, + products: Vec, + shipments: Shipments, +} + +#[derive(Debug, Serialize, Eq, PartialEq, Deserialize, Clone)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum OrderChannel { + Web, + Phone, + MobileApp, + Social, + Marketplace, + InStoreKiosk, + ScanAndGo, + SmartTv, + Mit, +} + +#[derive(Debug, Serialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Products { + item_name: String, + item_price: i64, + item_quantity: i32, +} + +#[derive(Debug, Serialize, Eq, PartialEq, Clone)] +pub struct Shipments { + destination: Destination, +} + +#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Destination { + full_name: Secret, + organization: Option, + email: Option, + address: Address, +} + +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Address { + street_address: Secret, + unit: Option>, + postal_code: Secret, + city: String, + province_code: Secret, + country_code: common_enums::CountryAlpha2, +} + +#[derive(Debug, Serialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SignifydPaymentsSaleRequest { + order_id: String, + purchase: Purchase, + decision_delivery: DecisionDelivery, +} + +impl TryFrom<&frm_types::FrmSaleRouterData> for SignifydPaymentsSaleRequest { + type Error = error_stack::Report; + fn try_from(item: &frm_types::FrmSaleRouterData) -> Result { + let products = item + .request + .get_order_details()? + .iter() + .map(|order_detail| Products { + item_name: order_detail.product_name.clone(), + item_price: order_detail.amount, + item_quantity: i32::from(order_detail.quantity), + }) + .collect::>(); + let ship_address = item.get_shipping_address()?; + let street_addr = ship_address.get_line1()?; + let city_addr = ship_address.get_city()?; + let zip_code_addr = ship_address.get_zip()?; + let country_code_addr = ship_address.get_country()?; + let _first_name_addr = ship_address.get_first_name()?; + let _last_name_addr = ship_address.get_last_name()?; + let address: Address = Address { + street_address: street_addr.clone(), + unit: None, + postal_code: zip_code_addr.clone(), + city: city_addr.clone(), + province_code: zip_code_addr.clone(), + country_code: country_code_addr.to_owned(), + }; + let destination: Destination = Destination { + full_name: ship_address.get_full_name().unwrap_or_default(), + organization: None, + email: None, + address, + }; + + let created_at = common_utils::date_time::now(); + let order_channel = OrderChannel::Web; + let shipments = Shipments { destination }; + let purchase = Purchase { + created_at, + order_channel, + total_price: item.request.amount, + products, + shipments, + }; + Ok(Self { + order_id: item.attempt_id.clone(), + purchase, + decision_delivery: DecisionDelivery::Sync, + }) + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Decision { + #[serde(with = "common_utils::custom_serde::iso8601")] + created_at: PrimitiveDateTime, + checkpoint_action: SignifydPaymentStatus, + checkpoint_action_reason: Option, + checkpoint_action_policy: Option, + score: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum SignifydPaymentStatus { + Accept, + Challenge, + Credit, + Hold, + Reject, +} + +impl From for storage_enums::FraudCheckStatus { + fn from(item: SignifydPaymentStatus) -> Self { + match item { + SignifydPaymentStatus::Accept => Self::Legit, + SignifydPaymentStatus::Reject => Self::Fraud, + SignifydPaymentStatus::Hold => Self::ManualReview, + SignifydPaymentStatus::Challenge | SignifydPaymentStatus::Credit => Self::Pending, + } + } +} +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SignifydPaymentsResponse { + signifyd_id: i64, + order_id: String, + decision: Decision, +} + +impl + TryFrom> + for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(frm_types::FraudCheckResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.order_id), + status: storage_enums::FraudCheckStatus::from( + item.response.decision.checkpoint_action, + ), + connector_metadata: None, + score: item.response.decision.score.and_then(|data| data.to_i32()), + reason: item + .response + .decision + .checkpoint_action_reason + .map(serde_json::Value::from), + }), + ..item.data + }) + } +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct SignifydErrorResponse { + pub messages: Vec, + pub errors: serde_json::Value, +} + +#[derive(Debug, Serialize, Eq, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Transactions { + transaction_id: String, + gateway_status_code: String, + payment_method: storage_enums::PaymentMethod, + amount: i64, + currency: storage_enums::Currency, +} + +#[derive(Debug, Serialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SignifydPaymentsTransactionRequest { + order_id: String, + checkout_id: String, + transactions: Transactions, +} + +impl From for GatewayStatusCode { + fn from(item: storage_enums::AttemptStatus) -> Self { + match item { + storage_enums::AttemptStatus::Pending => Self::Pending, + storage_enums::AttemptStatus::Failure => Self::Failure, + storage_enums::AttemptStatus::Charged => Self::Success, + _ => Self::Pending, + } + } +} + +impl TryFrom<&frm_types::FrmTransactionRouterData> for SignifydPaymentsTransactionRequest { + type Error = error_stack::Report; + fn try_from(item: &frm_types::FrmTransactionRouterData) -> Result { + let currency = item.request.get_currency()?; + let transactions = Transactions { + amount: item.request.amount, + transaction_id: item.clone().payment_id, + gateway_status_code: GatewayStatusCode::from(item.status).to_string(), + payment_method: item.payment_method, + currency, + }; + Ok(Self { + order_id: item.attempt_id.clone(), + checkout_id: item.payment_id.clone(), + transactions, + }) + } +} + +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + PartialEq, + serde::Serialize, + serde::Deserialize, + strum::Display, + strum::EnumString, +)] +#[strum(serialize_all = "SCREAMING_SNAKE_CASE")] +pub enum GatewayStatusCode { + Success, + Failure, + #[default] + Pending, + Error, + Cancelled, + Expired, + SoftDecline, +} + +#[derive(Debug, Serialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SignifydPaymentsCheckoutRequest { + checkout_id: String, + order_id: String, + purchase: Purchase, +} + +impl TryFrom<&frm_types::FrmCheckoutRouterData> for SignifydPaymentsCheckoutRequest { + type Error = error_stack::Report; + fn try_from(item: &frm_types::FrmCheckoutRouterData) -> Result { + let products = item + .request + .get_order_details()? + .iter() + .map(|order_detail| Products { + item_name: order_detail.product_name.clone(), + item_price: order_detail.amount, + item_quantity: i32::from(order_detail.quantity), + }) + .collect::>(); + let ship_address = item.get_shipping_address()?; + let street_addr = ship_address.get_line1()?; + let city_addr = ship_address.get_city()?; + let zip_code_addr = ship_address.get_zip()?; + let country_code_addr = ship_address.get_country()?; + let _first_name_addr = ship_address.get_first_name()?; + let _last_name_addr = ship_address.get_last_name()?; + let address: Address = Address { + street_address: street_addr.clone(), + unit: None, + postal_code: zip_code_addr.clone(), + city: city_addr.clone(), + province_code: zip_code_addr.clone(), + country_code: country_code_addr.to_owned(), + }; + let destination: Destination = Destination { + full_name: ship_address.get_full_name().unwrap_or_default(), + organization: None, + email: None, + address, + }; + let created_at = common_utils::date_time::now(); + let order_channel = OrderChannel::Web; + let shipments: Shipments = Shipments { destination }; + let purchase = Purchase { + created_at, + order_channel, + total_price: item.request.amount, + products, + shipments, + }; + Ok(Self { + checkout_id: item.payment_id.clone(), + order_id: item.attempt_id.clone(), + purchase, + }) + } +} + +#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "camelCase")] +pub struct FrmFullfillmentSignifydApiRequest { + pub order_id: String, + pub fulfillment_status: Option, + pub fulfillments: Vec, +} + +#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] +#[serde(untagged)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "camelCase")] +pub enum FulfillmentStatus { + PARTIAL, + COMPLETE, + REPLACEMENT, + CANCELED, +} + +#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "camelCase")] +pub struct Fulfillments { + pub shipment_id: String, + pub products: Option>, + pub destination: Destination, +} + +#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "camelCase")] +pub struct Product { + pub item_name: String, + pub item_quantity: i64, + pub item_id: String, +} + +impl From for FrmFullfillmentSignifydApiRequest { + fn from(req: FrmFulfillmentRequest) -> Self { + Self { + order_id: req.order_id, + fulfillment_status: req.fulfillment_status.map(FulfillmentStatus::from), + fulfillments: req + .fulfillments + .iter() + .map(|f| Fulfillments::from(f.clone())) + .collect(), + } + } +} + +impl From for FulfillmentStatus { + fn from(status: core_types::FulfillmentStatus) -> Self { + match status { + core_types::FulfillmentStatus::PARTIAL => Self::PARTIAL, + core_types::FulfillmentStatus::COMPLETE => Self::COMPLETE, + core_types::FulfillmentStatus::REPLACEMENT => Self::REPLACEMENT, + core_types::FulfillmentStatus::CANCELED => Self::CANCELED, + } + } +} + +impl From for Fulfillments { + fn from(fulfillment: core_types::Fulfillments) -> Self { + Self { + shipment_id: fulfillment.shipment_id, + products: fulfillment + .products + .map(|products| products.iter().map(|p| Product::from(p.clone())).collect()), + destination: Destination::from(fulfillment.destination), + } + } +} + +impl From for Product { + fn from(product: core_types::Product) -> Self { + Self { + item_name: product.item_name, + item_quantity: product.item_quantity, + item_id: product.item_id, + } + } +} + +impl From for Destination { + fn from(destination: core_types::Destination) -> Self { + Self { + full_name: destination.full_name, + organization: destination.organization, + email: destination.email, + address: Address::from(destination.address), + } + } +} + +impl From for Address { + fn from(address: core_types::Address) -> Self { + Self { + street_address: address.street_address, + unit: address.unit, + postal_code: address.postal_code, + city: address.city, + province_code: address.province_code, + country_code: address.country_code, + } + } +} + +#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "camelCase")] +pub struct FrmFullfillmentSignifydApiResponse { + pub order_id: String, + pub shipment_ids: Vec, +} + +impl + TryFrom< + ResponseRouterData< + Fulfillment, + FrmFullfillmentSignifydApiResponse, + frm_types::FraudCheckFulfillmentData, + frm_types::FraudCheckResponseData, + >, + > + for types::RouterData< + Fulfillment, + frm_types::FraudCheckFulfillmentData, + frm_types::FraudCheckResponseData, + > +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + Fulfillment, + FrmFullfillmentSignifydApiResponse, + frm_types::FraudCheckFulfillmentData, + frm_types::FraudCheckResponseData, + >, + ) -> Result { + Ok(Self { + response: Ok(frm_types::FraudCheckResponseData::FulfillmentResponse { + order_id: item.response.order_id, + shipment_ids: item.response.shipment_ids, + }), + ..item.data + }) + } +} + +#[derive(Debug, Serialize, Eq, PartialEq, Clone)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "camelCase")] +pub struct SignifydRefund { + method: RefundMethod, + amount: String, + currency: storage_enums::Currency, +} + +#[derive(Debug, Serialize, Eq, PartialEq)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "camelCase")] +pub struct SignifydPaymentsRecordReturnRequest { + order_id: String, + return_id: String, + refund_transaction_id: Option, + refund: SignifydRefund, +} + +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum RefundMethod { + StoreCredit, + OriginalPaymentInstrument, + NewPaymentInstrument, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "camelCase")] +pub struct SignifydPaymentsRecordReturnResponse { + return_id: String, + order_id: String, +} + +impl + TryFrom< + ResponseRouterData< + F, + SignifydPaymentsRecordReturnResponse, + T, + frm_types::FraudCheckResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + SignifydPaymentsRecordReturnResponse, + T, + frm_types::FraudCheckResponseData, + >, + ) -> Result { + Ok(Self { + response: Ok(frm_types::FraudCheckResponseData::RecordReturnResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.order_id), + return_id: Some(item.response.return_id.to_string()), + connector_metadata: None, + }), + ..item.data + }) + } +} + +impl TryFrom<&frm_types::FrmRecordReturnRouterData> for SignifydPaymentsRecordReturnRequest { + type Error = error_stack::Report; + fn try_from(item: &frm_types::FrmRecordReturnRouterData) -> Result { + let currency = item.request.get_currency()?; + let refund = SignifydRefund { + method: item.request.refund_method.clone(), + amount: item.request.amount.to_string(), + currency, + }; + Ok(Self { + return_id: uuid::Uuid::new_v4().to_string(), + refund_transaction_id: item.request.refund_transaction_id.clone(), + refund, + order_id: item.attempt_id.clone(), + }) + } +} diff --git a/crates/router/src/connector/signifyd/transformers/auth.rs b/crates/router/src/connector/signifyd/transformers/auth.rs new file mode 100644 index 000000000000..cc5867aea366 --- /dev/null +++ b/crates/router/src/connector/signifyd/transformers/auth.rs @@ -0,0 +1,20 @@ +use error_stack; +use masking::Secret; + +use crate::{core::errors, types}; + +pub struct SignifydAuthType { + pub api_key: Secret, +} + +impl TryFrom<&types::ConnectorAuthType> for SignifydAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + match auth_type { + types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + api_key: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 72f18ce56d6c..3a28f777907f 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -9,7 +9,7 @@ use common_utils::{ }; use data_models::mandates::AcceptanceType; use error_stack::{IntoReport, ResultExt}; -use masking::{ExposeInterface, ExposeOptionInterface, PeekInterface, Secret}; +use masking::{ExposeInterface, ExposeOptionInterface, Secret}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; use url::Url; @@ -17,7 +17,9 @@ use uuid::Uuid; use crate::{ collect_missing_value_keys, - connector::utils::{self as connector_util, ApplePay, PaymentsPreProcessingData, RouterData}, + connector::utils::{ + self as connector_util, ApplePay, ApplePayDecrypt, PaymentsPreProcessingData, RouterData, + }, core::errors, services, types::{ @@ -1474,24 +1476,8 @@ impl TryFrom<(&payments::WalletData, Option)> if let Some(types::PaymentMethodToken::ApplePayDecrypt(decrypt_data)) = payment_method_token { - let expiry_year_4_digit = Secret::new(format!( - "20{}", - decrypt_data - .clone() - .application_expiration_date - .peek() - .get(0..2) - .ok_or(errors::ConnectorError::RequestEncodingFailed)? - )); - let exp_month = Secret::new( - decrypt_data - .clone() - .application_expiration_date - .peek() - .get(2..4) - .ok_or(errors::ConnectorError::RequestEncodingFailed)? - .to_owned(), - ); + let expiry_year_4_digit = decrypt_data.get_four_digit_expiry_year()?; + let exp_month = decrypt_data.get_expiry_month()?; Some(Self::Wallet(StripeWallet::ApplePayPredecryptToken( Box::new(StripeApplePayPredecrypt { diff --git a/crates/router/src/connector/trustpay.rs b/crates/router/src/connector/trustpay.rs index 303ba8f67824..b41b0aeb8548 100644 --- a/crates/router/src/connector/trustpay.rs +++ b/crates/router/src/connector/trustpay.rs @@ -139,9 +139,11 @@ impl ConnectorCommon for Trustpay { message: option_error_code_message .map(|error_code_message| error_code_message.error_code) .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), - reason: reason.or(response_data.description), + reason: reason + .or(response_data.description) + .or(response_data.payment_description), attempt_status: None, - connector_transaction_id: None, + connector_transaction_id: response_data.instance_id, }) } Err(error_msg) => { @@ -360,19 +362,7 @@ impl ConnectorIntegration CustomResult { - let response: trustpay::TrustPayTransactionStatusErrorResponse = res - .response - .parse_struct("trustpay transaction status ErrorResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - Ok(ErrorResponse { - status_code: res.status_code, - code: response.status.to_string(), - // message vary for the same code, so relying on code alone as it is unique - message: response.status.to_string(), - reason: Some(response.payment_description), - attempt_status: None, - connector_transaction_id: None, - }) + self.build_error_response(res) } fn handle_response( diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index e985eff11976..270a702bd6ec 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -722,13 +722,13 @@ fn handle_cards_response( reason: msg, status_code, attempt_status: None, - connector_transaction_id: None, + connector_transaction_id: Some(response.instance_id.clone()), }) } else { None }; let payment_response_data = types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(response.instance_id), + resource_id: types::ResponseId::ConnectorTransactionId(response.instance_id.clone()), redirection_data, mandate_reference: None, connector_metadata: None, @@ -825,14 +825,24 @@ fn handle_bank_redirects_sync_response( reason: reason_info.reason.reject_reason, status_code, attempt_status: None, - connector_transaction_id: None, + connector_transaction_id: Some( + response + .payment_information + .references + .payment_request_id + .clone(), + ), }) } else { None }; let payment_response_data = types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId( - response.payment_information.references.payment_request_id, + response + .payment_information + .references + .payment_request_id + .clone(), ), redirection_data: None, mandate_reference: None, @@ -1637,16 +1647,13 @@ pub struct Errors { } #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct TrustpayErrorResponse { pub status: i64, pub description: Option, pub errors: Option>, -} - -#[derive(Deserialize)] -pub struct TrustPayTransactionStatusErrorResponse { - pub status: i64, - pub payment_description: String, + pub instance_id: Option, + pub payment_description: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 803c511f3a6b..3990fc9c7e47 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -17,6 +17,8 @@ use once_cell::sync::Lazy; use regex::Regex; use serde::Serializer; +#[cfg(feature = "frm")] +use crate::types::{fraud_check, storage::enums as storage_enums}; use crate::{ consts, core::{ @@ -26,7 +28,7 @@ use crate::{ pii::PeekInterface, types::{ self, api, storage::payment_attempt::PaymentAttemptExt, transformers::ForeignTryFrom, - PaymentsCancelData, ResponseId, + ApplePayPredecryptData, PaymentsCancelData, ResponseId, }, utils::{OptionExt, ValueExt}, }; @@ -853,6 +855,33 @@ impl ApplePay for payments::ApplePayWalletData { } } +pub trait ApplePayDecrypt { + fn get_expiry_month(&self) -> Result, Error>; + fn get_four_digit_expiry_year(&self) -> Result, Error>; +} + +impl ApplePayDecrypt for Box { + fn get_four_digit_expiry_year(&self) -> Result, Error> { + Ok(Secret::new(format!( + "20{}", + self.application_expiration_date + .peek() + .get(0..2) + .ok_or(errors::ConnectorError::RequestEncodingFailed)? + ))) + } + + fn get_expiry_month(&self) -> Result, Error> { + Ok(Secret::new( + self.application_expiration_date + .peek() + .get(2..4) + .ok_or(errors::ConnectorError::RequestEncodingFailed)? + .to_owned(), + )) + } +} + pub trait CryptoData { fn get_pay_currency(&self) -> Result; } @@ -1575,3 +1604,51 @@ pub fn validate_currency( } Ok(()) } + +#[cfg(feature = "frm")] +pub trait FraudCheckSaleRequest { + fn get_order_details(&self) -> Result, Error>; +} +#[cfg(feature = "frm")] +impl FraudCheckSaleRequest for fraud_check::FraudCheckSaleData { + fn get_order_details(&self) -> Result, Error> { + self.order_details + .clone() + .ok_or_else(missing_field_err("order_details")) + } +} + +#[cfg(feature = "frm")] +pub trait FraudCheckCheckoutRequest { + fn get_order_details(&self) -> Result, Error>; +} +#[cfg(feature = "frm")] +impl FraudCheckCheckoutRequest for fraud_check::FraudCheckCheckoutData { + fn get_order_details(&self) -> Result, Error> { + self.order_details + .clone() + .ok_or_else(missing_field_err("order_details")) + } +} + +#[cfg(feature = "frm")] +pub trait FraudCheckTransactionRequest { + fn get_currency(&self) -> Result; +} +#[cfg(feature = "frm")] +impl FraudCheckTransactionRequest for fraud_check::FraudCheckTransactionData { + fn get_currency(&self) -> Result { + self.currency.ok_or_else(missing_field_err("currency")) + } +} + +#[cfg(feature = "frm")] +pub trait FraudCheckRecordReturnRequest { + fn get_currency(&self) -> Result; +} +#[cfg(feature = "frm")] +impl FraudCheckRecordReturnRequest for fraud_check::FraudCheckRecordReturnData { + fn get_currency(&self) -> Result { + self.currency.ok_or_else(missing_field_err("currency")) + } +} diff --git a/crates/router/src/core.rs b/crates/router/src/core.rs index 08de9cf80384..0bd197ee22e9 100644 --- a/crates/router/src/core.rs +++ b/crates/router/src/core.rs @@ -5,12 +5,16 @@ pub mod cache; pub mod cards_info; pub mod conditional_config; pub mod configs; +#[cfg(feature = "olap")] +pub mod connector_onboarding; #[cfg(any(feature = "olap", feature = "oltp"))] pub mod currency; pub mod customers; pub mod disputes; pub mod errors; pub mod files; +#[cfg(feature = "frm")] +pub mod fraud_check; pub mod gsm; pub mod locker_migration; pub mod mandate; @@ -20,6 +24,7 @@ pub mod payment_methods; pub mod payments; #[cfg(feature = "payouts")] pub mod payouts; +pub mod pm_auth; pub mod refunds; pub mod routing; pub mod surcharge_decision_config; diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 107e8f8859d6..113bc7d677d2 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -10,9 +10,10 @@ use common_utils::{ ext_traits::{AsyncExt, ConfigExt, Encode, ValueExt}, pii, }; -use error_stack::{report, FutureExt, ResultExt}; +use error_stack::{report, FutureExt, IntoReport, ResultExt}; use futures::future::try_join_all; use masking::{PeekInterface, Secret}; +use pm_auth::connector::plaid::transformers::PlaidAuthType; use uuid::Uuid; use crate::{ @@ -762,7 +763,7 @@ pub async fn create_payment_connector( ) .await?; - let routable_connector = + let mut routable_connector = api_enums::RoutableConnectors::from_str(&req.connector_name.to_string()).ok(); let business_profile = state @@ -773,6 +774,30 @@ pub async fn create_payment_connector( id: profile_id.to_owned(), })?; + let pm_auth_connector = + api_enums::convert_pm_auth_connector(req.connector_name.to_string().as_str()); + + let is_unroutable_connector = if pm_auth_connector.is_some() { + if req.connector_type != api_enums::ConnectorType::PaymentMethodAuth { + return Err(errors::ApiErrorResponse::InvalidRequestData { + message: "Invalid connector type given".to_string(), + }) + .into_report(); + } + true + } else { + let routable_connector_option = req + .connector_name + .to_string() + .parse() + .into_report() + .change_context(errors::ApiErrorResponse::InvalidRequestData { + message: "Invalid connector name given".to_string(), + })?; + routable_connector = Some(routable_connector_option); + false + }; + // If connector label is not passed in the request, generate one let connector_label = req .connector_label @@ -877,6 +902,20 @@ pub async fn create_payment_connector( api_enums::ConnectorStatus::Active, )?; + if req.connector_type != api_enums::ConnectorType::PaymentMethodAuth { + if let Some(val) = req.pm_auth_config.clone() { + validate_pm_auth( + val, + &*state.clone().store, + merchant_id.clone().as_str(), + &key_store, + merchant_account, + &Some(profile_id.clone()), + ) + .await?; + } + } + let merchant_connector_account = domain::MerchantConnectorAccount { merchant_id: merchant_id.to_string(), connector_type: req.connector_type, @@ -948,7 +987,7 @@ pub async fn create_payment_connector( #[cfg(feature = "connector_choice_mca_id")] merchant_connector_id: Some(mca.merchant_connector_id.clone()), #[cfg(not(feature = "connector_choice_mca_id"))] - sub_label: req.business_sub_label, + sub_label: req.business_sub_label.clone(), }; if !default_routing_config.contains(&choice) { @@ -956,7 +995,7 @@ pub async fn create_payment_connector( routing_helpers::update_merchant_default_config( &*state.store, merchant_id, - default_routing_config, + default_routing_config.clone(), ) .await?; } @@ -965,7 +1004,7 @@ pub async fn create_payment_connector( routing_helpers::update_merchant_default_config( &*state.store, &profile_id.clone(), - default_routing_config_for_profile, + default_routing_config_for_profile.clone(), ) .await?; } @@ -980,10 +1019,92 @@ pub async fn create_payment_connector( ], ); + if !is_unroutable_connector { + if let Some(routable_connector_val) = routable_connector { + let choice = routing_types::RoutableConnectorChoice { + #[cfg(feature = "backwards_compatibility")] + choice_kind: routing_types::RoutableChoiceKind::FullStruct, + connector: routable_connector_val, + #[cfg(feature = "connector_choice_mca_id")] + merchant_connector_id: Some(mca.merchant_connector_id.clone()), + #[cfg(not(feature = "connector_choice_mca_id"))] + sub_label: req.business_sub_label.clone(), + }; + + if !default_routing_config.contains(&choice) { + default_routing_config.push(choice.clone()); + routing_helpers::update_merchant_default_config( + &*state.clone().store, + merchant_id, + default_routing_config, + ) + .await?; + } + + if !default_routing_config_for_profile.contains(&choice) { + default_routing_config_for_profile.push(choice); + routing_helpers::update_merchant_default_config( + &*state.store, + &profile_id, + default_routing_config_for_profile, + ) + .await?; + } + } + }; + let mca_response = mca.try_into()?; Ok(service_api::ApplicationResponse::Json(mca_response)) } +async fn validate_pm_auth( + val: serde_json::Value, + db: &dyn StorageInterface, + merchant_id: &str, + key_store: &domain::MerchantKeyStore, + merchant_account: domain::MerchantAccount, + profile_id: &Option, +) -> RouterResponse<()> { + let config = serde_json::from_value::(val) + .into_report() + .change_context(errors::ApiErrorResponse::InvalidRequestData { + message: "invalid data received for payment method auth config".to_string(), + }) + .attach_printable("Failed to deserialize Payment Method Auth config")?; + + let all_mcas = db + .find_merchant_connector_account_by_merchant_id_and_disabled_list( + merchant_id, + true, + key_store, + ) + .await + .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: merchant_account.merchant_id.clone(), + })?; + + for conn_choice in config.enabled_payment_methods { + let pm_auth_mca = all_mcas + .clone() + .into_iter() + .find(|mca| mca.merchant_connector_id == conn_choice.mca_id) + .ok_or(errors::ApiErrorResponse::GenericNotFoundError { + message: "payment method auth connector account not found".to_string(), + }) + .into_report()?; + + if &pm_auth_mca.profile_id != profile_id { + return Err(errors::ApiErrorResponse::GenericNotFoundError { + message: "payment method auth profile_id differs from connector profile_id" + .to_string(), + }) + .into_report(); + } + } + + Ok(services::ApplicationResponse::StatusOk) +} + pub async fn retrieve_payment_connector( state: AppState, merchant_id: String, @@ -1066,7 +1187,7 @@ pub async fn update_payment_connector( .await .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; - let _merchant_account = db + let merchant_account = db .find_merchant_account_by_merchant_id(merchant_id, &key_store) .await .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; @@ -1106,6 +1227,20 @@ pub async fn update_payment_connector( let (connector_status, disabled) = validate_status_and_disabled(req.status, req.disabled, auth, mca.status)?; + if req.connector_type != api_enums::ConnectorType::PaymentMethodAuth { + if let Some(val) = req.pm_auth_config.clone() { + validate_pm_auth( + val, + db, + merchant_id, + &key_store, + merchant_account, + &mca.profile_id, + ) + .await?; + } + } + let payment_connector = storage::MerchantConnectorAccountUpdate::Update { merchant_id: None, connector_type: Some(req.connector_type), @@ -1716,9 +1851,13 @@ pub(crate) fn validate_auth_and_metadata_type( zen::transformers::ZenAuthType::try_from(val)?; Ok(()) } - api_enums::Connector::Signifyd | api_enums::Connector::Plaid => { - Err(report!(errors::ConnectorError::InvalidConnectorName) - .attach_printable(format!("invalid connector name: {connector_name}"))) + api_enums::Connector::Signifyd => { + signifyd::transformers::SignifydAuthType::try_from(val)?; + Ok(()) + } + api_enums::Connector::Plaid => { + PlaidAuthType::foreign_try_from(val)?; + Ok(()) } } } diff --git a/crates/router/src/core/connector_onboarding.rs b/crates/router/src/core/connector_onboarding.rs new file mode 100644 index 000000000000..e48026edc2d5 --- /dev/null +++ b/crates/router/src/core/connector_onboarding.rs @@ -0,0 +1,96 @@ +use api_models::{connector_onboarding as api, enums}; +use error_stack::ResultExt; +use masking::Secret; + +use crate::{ + core::errors::{ApiErrorResponse, RouterResponse, RouterResult}, + services::{authentication as auth, ApplicationResponse}, + types::{self as oss_types}, + utils::connector_onboarding as utils, + AppState, +}; + +pub mod paypal; + +#[async_trait::async_trait] +pub trait AccessToken { + async fn access_token(state: &AppState) -> RouterResult; +} + +pub async fn get_action_url( + state: AppState, + request: api::ActionUrlRequest, +) -> RouterResponse { + let connector_onboarding_conf = state.conf.connector_onboarding.clone(); + let is_enabled = utils::is_enabled(request.connector, &connector_onboarding_conf); + + match (is_enabled, request.connector) { + (Some(true), enums::Connector::Paypal) => { + let action_url = Box::pin(paypal::get_action_url_from_paypal( + state, + request.connector_id, + request.return_url, + )) + .await?; + Ok(ApplicationResponse::Json(api::ActionUrlResponse::PayPal( + api::PayPalActionUrlResponse { action_url }, + ))) + } + _ => Err(ApiErrorResponse::FlowNotSupported { + flow: "Connector onboarding".to_string(), + connector: request.connector.to_string(), + } + .into()), + } +} + +pub async fn sync_onboarding_status( + state: AppState, + user_from_token: auth::UserFromToken, + request: api::OnboardingSyncRequest, +) -> RouterResponse { + let merchant_account = user_from_token + .get_merchant_account(state.clone()) + .await + .change_context(ApiErrorResponse::MerchantAccountNotFound)?; + let connector_onboarding_conf = state.conf.connector_onboarding.clone(); + let is_enabled = utils::is_enabled(request.connector, &connector_onboarding_conf); + + match (is_enabled, request.connector) { + (Some(true), enums::Connector::Paypal) => { + let status = Box::pin(paypal::sync_merchant_onboarding_status( + state.clone(), + request.connector_id.clone(), + )) + .await?; + if let api::OnboardingStatus::PayPal(api::PayPalOnboardingStatus::Success( + ref inner_data, + )) = status + { + let connector_onboarding_conf = state.conf.connector_onboarding.clone(); + let auth_details = oss_types::ConnectorAuthType::SignatureKey { + api_key: connector_onboarding_conf.paypal.client_secret, + key1: connector_onboarding_conf.paypal.client_id, + api_secret: Secret::new(inner_data.payer_id.clone()), + }; + let some_data = paypal::update_mca( + &state, + &merchant_account, + request.connector_id.to_owned(), + auth_details, + ) + .await?; + + return Ok(ApplicationResponse::Json(api::OnboardingStatus::PayPal( + api::PayPalOnboardingStatus::ConnectorIntegrated(some_data), + ))); + } + Ok(ApplicationResponse::Json(status)) + } + _ => Err(ApiErrorResponse::FlowNotSupported { + flow: "Connector onboarding".to_string(), + connector: request.connector.to_string(), + } + .into()), + } +} diff --git a/crates/router/src/core/connector_onboarding/paypal.rs b/crates/router/src/core/connector_onboarding/paypal.rs new file mode 100644 index 000000000000..30aa69067b5d --- /dev/null +++ b/crates/router/src/core/connector_onboarding/paypal.rs @@ -0,0 +1,174 @@ +use api_models::{admin::MerchantConnectorUpdate, connector_onboarding as api}; +use common_utils::ext_traits::Encode; +use error_stack::{IntoReport, ResultExt}; +use masking::{ExposeInterface, PeekInterface, Secret}; + +use crate::{ + core::{ + admin, + errors::{ApiErrorResponse, RouterResult}, + }, + services::{send_request, ApplicationResponse, Request}, + types::{self as oss_types, api as oss_api_types, api::connector_onboarding as types}, + utils::connector_onboarding as utils, + AppState, +}; + +fn build_referral_url(state: AppState) -> String { + format!( + "{}v2/customer/partner-referrals", + state.conf.connectors.paypal.base_url + ) +} + +async fn build_referral_request( + state: AppState, + connector_id: String, + return_url: String, +) -> RouterResult { + let access_token = utils::paypal::generate_access_token(state.clone()).await?; + let request_body = types::paypal::PartnerReferralRequest::new(connector_id, return_url); + + utils::paypal::build_paypal_post_request( + build_referral_url(state), + request_body, + access_token.token.expose(), + ) +} + +pub async fn get_action_url_from_paypal( + state: AppState, + connector_id: String, + return_url: String, +) -> RouterResult { + let referral_request = Box::pin(build_referral_request( + state.clone(), + connector_id, + return_url, + )) + .await?; + let referral_response = send_request(&state, referral_request, None) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to send request to paypal referrals")?; + + let parsed_response: types::paypal::PartnerReferralResponse = referral_response + .json() + .await + .into_report() + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to parse paypal response")?; + + parsed_response.extract_action_url() +} + +fn merchant_onboarding_status_url(state: AppState, tracking_id: String) -> String { + let partner_id = state.conf.connector_onboarding.paypal.partner_id.to_owned(); + format!( + "{}v1/customer/partners/{}/merchant-integrations?tracking_id={}", + state.conf.connectors.paypal.base_url, + partner_id.expose(), + tracking_id + ) +} + +pub async fn sync_merchant_onboarding_status( + state: AppState, + tracking_id: String, +) -> RouterResult { + let access_token = utils::paypal::generate_access_token(state.clone()).await?; + + let Some(seller_status_response) = + find_paypal_merchant_by_tracking_id(state.clone(), tracking_id, &access_token).await? + else { + return Ok(api::OnboardingStatus::PayPal( + api::PayPalOnboardingStatus::AccountNotFound, + )); + }; + + let merchant_details_url = seller_status_response + .extract_merchant_details_url(&state.conf.connectors.paypal.base_url)?; + + let merchant_details_request = + utils::paypal::build_paypal_get_request(merchant_details_url, access_token.token.expose())?; + + let merchant_details_response = send_request(&state, merchant_details_request, None) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to send request to paypal merchant details")?; + + let parsed_response: types::paypal::SellerStatusDetailsResponse = merchant_details_response + .json() + .await + .into_report() + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to parse paypal merchant details response")?; + + let eligibity = parsed_response.get_eligibility_status().await?; + Ok(api::OnboardingStatus::PayPal(eligibity)) +} + +async fn find_paypal_merchant_by_tracking_id( + state: AppState, + tracking_id: String, + access_token: &oss_types::AccessToken, +) -> RouterResult> { + let seller_status_request = utils::paypal::build_paypal_get_request( + merchant_onboarding_status_url(state.clone(), tracking_id), + access_token.token.peek().to_string(), + )?; + let seller_status_response = send_request(&state, seller_status_request, None) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to send request to paypal onboarding status")?; + + if seller_status_response.status().is_success() { + return Ok(Some( + seller_status_response + .json() + .await + .into_report() + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to parse paypal onboarding status response")?, + )); + } + Ok(None) +} + +pub async fn update_mca( + state: &AppState, + merchant_account: &oss_types::domain::MerchantAccount, + connector_id: String, + auth_details: oss_types::ConnectorAuthType, +) -> RouterResult { + let connector_auth_json = + Encode::::encode_to_value(&auth_details) + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Error while deserializing connector_account_details")?; + + let request = MerchantConnectorUpdate { + connector_type: common_enums::ConnectorType::PaymentProcessor, + connector_account_details: Some(Secret::new(connector_auth_json)), + disabled: Some(false), + status: Some(common_enums::ConnectorStatus::Active), + test_mode: None, + connector_label: None, + payment_methods_enabled: None, + metadata: None, + frm_configs: None, + connector_webhook_details: None, + pm_auth_config: None, + }; + let mca_response = admin::update_payment_connector( + state.clone(), + &merchant_account.merchant_id, + &connector_id, + request, + ) + .await?; + + match mca_response { + ApplicationResponse::Json(mca_data) => Ok(mca_data), + _ => Err(ApiErrorResponse::InternalServerError.into()), + } +} diff --git a/crates/router/src/core/errors/user.rs b/crates/router/src/core/errors/user.rs index 5e580b003408..03ca88056101 100644 --- a/crates/router/src/core/errors/user.rs +++ b/crates/router/src/core/errors/user.rs @@ -12,8 +12,14 @@ pub enum UserErrors { InternalServerError, #[error("InvalidCredentials")] InvalidCredentials, + #[error("UserNotFound")] + UserNotFound, #[error("UserExists")] UserExists, + #[error("LinkInvalid")] + LinkInvalid, + #[error("UnverifiedUser")] + UnverifiedUser, #[error("InvalidOldPassword")] InvalidOldPassword, #[error("EmailParsingError")] @@ -44,6 +50,8 @@ pub enum UserErrors { InvalidMetadataRequest, #[error("MerchantIdParsingError")] MerchantIdParsingError, + #[error("ChangePasswordError")] + ChangePasswordError, } impl common_utils::errors::ErrorSwitch for UserErrors { @@ -60,12 +68,27 @@ impl common_utils::errors::ErrorSwitch AER::Unauthorized(ApiError::new( + sub_code, + 2, + "Email doesn’t exist. Register", + None, + )), Self::UserExists => AER::BadRequest(ApiError::new( sub_code, 3, "An account already exists with this email", None, )), + Self::LinkInvalid => { + AER::Unauthorized(ApiError::new(sub_code, 4, "Invalid or expired link", None)) + } + Self::UnverifiedUser => AER::Unauthorized(ApiError::new( + sub_code, + 5, + "Kindly verify your account", + None, + )), Self::InvalidOldPassword => AER::BadRequest(ApiError::new( sub_code, 6, @@ -123,6 +146,12 @@ impl common_utils::errors::ErrorSwitch { AER::BadRequest(ApiError::new(sub_code, 28, "Invalid Merchant Id", None)) } + Self::ChangePasswordError => AER::BadRequest(ApiError::new( + sub_code, + 29, + "Old and new password cannot be same", + None, + )), } } } diff --git a/crates/router/src/core/errors/user/sample_data.rs b/crates/router/src/core/errors/user/sample_data.rs index 11233b27b5cd..84c6c9fa43a2 100644 --- a/crates/router/src/core/errors/user/sample_data.rs +++ b/crates/router/src/core/errors/user/sample_data.rs @@ -10,10 +10,6 @@ pub enum SampleDataError { InternalServerError, #[error("Data Does Not Exist")] DataDoesNotExist, - #[error("Server Error")] - DatabaseError, - #[error("Merchant Id Not Found")] - MerchantIdNotFound, #[error("Invalid Parameters")] InvalidParameters, #[error["Invalid Records"]] @@ -29,33 +25,21 @@ impl ErrorSwitch for SampleDataError { "Something went wrong", None, )), - Self::DatabaseError => ApiErrorResponse::InternalServerError(ApiError::new( - "SD", - 1, - "Server Error(DB is down)", - None, - )), Self::DataDoesNotExist => ApiErrorResponse::NotFound(ApiError::new( "SD", - 2, + 1, "Sample Data not present for given request", None, )), - Self::MerchantIdNotFound => ApiErrorResponse::BadRequest(ApiError::new( - "SD", - 3, - "Merchant ID not provided", - None, - )), Self::InvalidParameters => ApiErrorResponse::BadRequest(ApiError::new( "SD", - 4, + 2, "Invalid parameters to generate Sample Data", None, )), Self::InvalidRange => ApiErrorResponse::BadRequest(ApiError::new( "SD", - 5, + 3, "Records to be generated should be between range 10 and 100", None, )), @@ -67,7 +51,7 @@ impl ErrorSwitchFrom for SampleDataError { fn switch_from(error: &StorageError) -> Self { match matches!(error, StorageError::ValueNotFound(_)) { true => Self::DataDoesNotExist, - false => Self::DatabaseError, + false => Self::InternalServerError, } } } diff --git a/crates/router/src/core/errors/utils.rs b/crates/router/src/core/errors/utils.rs index b62abd0e336e..f00948b887e1 100644 --- a/crates/router/src/core/errors/utils.rs +++ b/crates/router/src/core/errors/utils.rs @@ -480,3 +480,25 @@ impl ConnectorErrorExt for error_stack::Result } } } + +pub trait RedisErrorExt { + #[track_caller] + fn to_redis_failed_response(self, key: &str) -> error_stack::Report; +} + +impl RedisErrorExt for error_stack::Report { + fn to_redis_failed_response(self, key: &str) -> error_stack::Report { + match self.current_context() { + errors::RedisError::NotFound => self.change_context( + errors::StorageError::ValueNotFound(format!("Data does not exist for key {key}",)), + ), + errors::RedisError::SetNxFailed => { + self.change_context(errors::StorageError::DuplicateValue { + entity: "redis", + key: Some(key.to_string()), + }) + } + _ => self.change_context(errors::StorageError::KVError), + } + } +} diff --git a/crates/router/src/core/fraud_check.rs b/crates/router/src/core/fraud_check.rs new file mode 100644 index 000000000000..55bd22baeec4 --- /dev/null +++ b/crates/router/src/core/fraud_check.rs @@ -0,0 +1,770 @@ +use std::fmt::Debug; + +use api_models::{admin::FrmConfigs, enums as api_enums, payments::AdditionalPaymentData}; +use error_stack::ResultExt; +use masking::PeekInterface; +use router_env::{ + logger, + tracing::{self, instrument}, +}; + +use self::{ + flows::{self as frm_flows, FeatureFrm}, + types::{ + self as frm_core_types, ConnectorDetailsCore, FrmConfigsObject, FrmData, FrmInfo, + PaymentDetails, PaymentToFrmData, + }, +}; +use super::errors::{ConnectorErrorExt, RouterResponse}; +use crate::{ + connector::signifyd::transformers::FrmFullfillmentSignifydApiRequest, + core::{ + errors::{self, RouterResult}, + payments::{ + self, flows::ConstructFlowSpecificData, helpers::get_additional_payment_data, + operations::BoxedOperation, + }, + utils as core_utils, + }, + db::StorageInterface, + routes::AppState, + services, + types::{ + self as oss_types, + api::{routing::FrmRoutingAlgorithm, Connector, FraudCheckConnectorData, Fulfillment}, + domain, fraud_check as frm_types, + storage::{ + enums::{ + AttemptStatus, FraudCheckLastStep, FraudCheckStatus, FraudCheckType, FrmSuggestion, + IntentStatus, + }, + fraud_check::{FraudCheck, FraudCheckUpdate}, + PaymentIntent, + }, + }, + utils::ValueExt, +}; +pub mod flows; +pub mod operation; +pub mod types; + +#[instrument(skip_all)] +pub async fn call_frm_service( + state: &AppState, + payment_data: &mut payments::PaymentData, + frm_data: FrmData, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + customer: &Option, +) -> RouterResult> +where + F: Send + Clone, + + // To create connector flow specific interface data + FrmData: ConstructFlowSpecificData, + oss_types::RouterData: FeatureFrm + Send, + + // To construct connector flow specific api + dyn Connector: services::api::ConnectorIntegration, +{ + let merchant_connector_account = payments::construct_profile_id_and_get_mca( + state, + merchant_account, + payment_data, + &frm_data.connector_details.connector_name, + None, + key_store, + false, + ) + .await?; + + let router_data = frm_data + .construct_router_data( + state, + &frm_data.connector_details.connector_name, + merchant_account, + key_store, + customer, + &merchant_connector_account, + ) + .await?; + let connector = + FraudCheckConnectorData::get_connector_by_name(&frm_data.connector_details.connector_name)?; + let router_data_res = router_data + .decide_frm_flows( + state, + &connector, + payments::CallConnectorAction::Trigger, + merchant_account, + ) + .await?; + + Ok(router_data_res) +} + +pub async fn should_call_frm( + merchant_account: &domain::MerchantAccount, + payment_data: &payments::PaymentData, + db: &dyn StorageInterface, + key_store: domain::MerchantKeyStore, +) -> RouterResult<( + bool, + Option, + Option, + Option, +)> +where + F: Send + Clone, +{ + match merchant_account.frm_routing_algorithm.clone() { + Some(frm_routing_algorithm_value) => { + let frm_routing_algorithm_struct: FrmRoutingAlgorithm = frm_routing_algorithm_value + .clone() + .parse_value("FrmRoutingAlgorithm") + .change_context(errors::ApiErrorResponse::MissingRequiredField { + field_name: "frm_routing_algorithm", + }) + .attach_printable("Data field not found in frm_routing_algorithm")?; + + let profile_id = core_utils::get_profile_id_from_business_details( + payment_data.payment_intent.business_country, + payment_data.payment_intent.business_label.as_ref(), + merchant_account, + payment_data.payment_intent.profile_id.as_ref(), + db, + false, + ) + .await + .attach_printable("Could not find profile id from business details")?; + + let merchant_connector_account_from_db_option = db + .find_merchant_connector_account_by_profile_id_connector_name( + &profile_id, + &frm_routing_algorithm_struct.data, + &key_store, + ) + .await + .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: merchant_account.merchant_id.clone(), + }) + .ok(); + + match merchant_connector_account_from_db_option { + Some(merchant_connector_account_from_db) => { + let frm_configs_option = merchant_connector_account_from_db + .frm_configs + .ok_or(errors::ApiErrorResponse::MissingRequiredField { + field_name: "frm_configs", + }) + .ok(); + match frm_configs_option { + Some(frm_configs_value) => { + let frm_configs_struct: Vec = frm_configs_value + .iter() + .map(|config| { config + .peek() + .clone() + .parse_value("FrmConfigs") + .change_context(errors::ApiErrorResponse::InvalidDataFormat { + field_name: "frm_configs".to_string(), + expected_format: r#"[{ "gateway": "stripe", "payment_methods": [{ "payment_method": "card","payment_method_types": [{"payment_method_type": "credit","card_networks": ["Visa"],"flow": "pre","action": "cancel_txn"}]}]}]"#.to_string(), + }) + }) + .collect::, _>>()?; + + let mut is_frm_connector_enabled = false; + let mut is_frm_pm_enabled = false; + let mut is_frm_pmt_enabled = false; + let filtered_frm_config = frm_configs_struct + .iter() + .filter(|frm_config| { + match ( + &payment_data.clone().payment_attempt.connector, + &frm_config.gateway, + ) { + (Some(current_connector), Some(configured_connector)) => { + let is_enabled = *current_connector + == configured_connector.to_string(); + if is_enabled { + is_frm_connector_enabled = true; + } + is_enabled + } + (None, _) | (_, None) => true, + } + }) + .collect::>(); + let filtered_payment_methods = filtered_frm_config + .iter() + .map(|frm_config| { + let filtered_frm_config_by_pm = frm_config + .payment_methods + .iter() + .filter(|frm_config_pm| { + match ( + payment_data.payment_attempt.payment_method, + frm_config_pm.payment_method, + ) { + ( + Some(current_pm), + Some(configured_connector_pm), + ) => { + let is_enabled = current_pm.to_string() + == configured_connector_pm.to_string(); + if is_enabled { + is_frm_pm_enabled = true; + } + is_enabled + } + (None, _) | (_, None) => true, + } + }) + .collect::>(); + filtered_frm_config_by_pm + }) + .collect::>() + .concat(); + let additional_payment_data = match &payment_data.payment_method_data { + Some(pmd) => { + let additional_payment_data = + get_additional_payment_data(pmd, db).await; + Some(additional_payment_data) + } + None => payment_data + .payment_attempt + .payment_method_data + .as_ref() + .map(|pm_data| { + pm_data.clone().parse_value::( + "AdditionalPaymentData", + ) + }) + .transpose() + .unwrap_or_default(), // Making this default in case of error as we don't want to fail payment for frm errors + }; + let filtered_payment_method_types = filtered_payment_methods + .iter() + .map(|frm_pm_config| { + let filtered_pm_config_by_pmt = frm_pm_config + .payment_method_types + .iter() + .filter(|frm_pm_config_by_pmt| { + match ( + &payment_data + .clone() + .payment_attempt + .payment_method_type, + frm_pm_config_by_pmt.payment_method_type, + ) { + (Some(curr), Some(conf)) + if curr.to_string() == conf.to_string() => + { + is_frm_pmt_enabled = true; + true + } + (None, Some(conf)) => match additional_payment_data + .clone() + { + Some(AdditionalPaymentData::Card(card)) => { + let card_type = card + .card_type + .unwrap_or_else(|| "debit".to_string()); + let is_enabled = card_type.to_lowercase() + == conf.to_string().to_lowercase(); + if is_enabled { + is_frm_pmt_enabled = true; + } + is_enabled + } + _ => false, + }, + _ => false, + } + }) + .collect::>(); + filtered_pm_config_by_pmt + }) + .collect::>() + .concat(); + let is_frm_enabled = + is_frm_connector_enabled && is_frm_pm_enabled && is_frm_pmt_enabled; + logger::debug!( + "frm_configs {:?} {:?} {:?} {:?}", + is_frm_connector_enabled, + is_frm_pm_enabled, + is_frm_pmt_enabled, + is_frm_enabled + ); + // filtered_frm_config... + // Panic Safety: we are first checking if the object is present... only if present, we try to fetch index 0 + let frm_configs_object = FrmConfigsObject { + frm_enabled_gateway: filtered_frm_config + .get(0) + .and_then(|c| c.gateway), + frm_enabled_pm: filtered_payment_methods + .get(0) + .and_then(|pm| pm.payment_method), + frm_enabled_pm_type: filtered_payment_method_types + .get(0) + .and_then(|pmt| pmt.payment_method_type), + frm_action: filtered_payment_method_types + // .clone() + .get(0) + .map(|pmt| pmt.action.clone()) + .unwrap_or(api_enums::FrmAction::ManualReview), + frm_preferred_flow_type: filtered_payment_method_types + .get(0) + .map(|pmt| pmt.flow.clone()) + .unwrap_or(api_enums::FrmPreferredFlowTypes::Pre), + }; + logger::debug!( + "frm_routing_configs: {:?} {:?} {:?} {:?}", + frm_routing_algorithm_struct, + profile_id, + frm_configs_object, + is_frm_enabled + ); + Ok(( + is_frm_enabled, + Some(frm_routing_algorithm_struct), + Some(profile_id.to_string()), + Some(frm_configs_object), + )) + } + None => { + logger::error!("Cannot find frm_configs for FRM provider"); + Ok((false, None, None, None)) + } + } + } + None => { + logger::error!("Cannot find merchant connector account for FRM provider"); + Ok((false, None, None, None)) + } + } + } + _ => Ok((false, None, None, None)), + } +} + +#[allow(clippy::too_many_arguments)] +pub async fn make_frm_data_and_fraud_check_operation<'a, F>( + _db: &dyn StorageInterface, + state: &AppState, + merchant_account: &domain::MerchantAccount, + payment_data: payments::PaymentData, + frm_routing_algorithm: FrmRoutingAlgorithm, + profile_id: String, + frm_configs: FrmConfigsObject, + _customer: &Option, +) -> RouterResult> +where + F: Send + Clone, +{ + let order_details = payment_data + .payment_intent + .order_details + .clone() + .or_else(|| + // when the order_details are present within the meta_data, we need to take those to support backward compatibility + payment_data.payment_intent.metadata.clone().and_then(|meta| { + let order_details = meta.peek().get("order_details").to_owned(); + order_details.map(|order| vec![masking::Secret::new(order.to_owned())]) + })) + .map(|order_details_value| { + order_details_value + .into_iter() + .map(|data| { + data.peek() + .to_owned() + .parse_value("OrderDetailsWithAmount") + .attach_printable("unable to parse OrderDetailsWithAmount") + }) + .collect::, _>>() + .unwrap_or_default() + }); + + let frm_connector_details = ConnectorDetailsCore { + connector_name: frm_routing_algorithm.data, + profile_id, + }; + + let payment_to_frm_data = PaymentToFrmData { + amount: payment_data.amount, + payment_intent: payment_data.payment_intent, + payment_attempt: payment_data.payment_attempt, + merchant_account: merchant_account.to_owned(), + address: payment_data.address.clone(), + connector_details: frm_connector_details.clone(), + order_details, + }; + + let fraud_check_operation: operation::BoxedFraudCheckOperation = + match frm_configs.frm_preferred_flow_type { + api_enums::FrmPreferredFlowTypes::Pre => Box::new(operation::FraudCheckPre), + api_enums::FrmPreferredFlowTypes::Post => Box::new(operation::FraudCheckPost), + }; + let frm_data = fraud_check_operation + .to_get_tracker()? + .get_trackers(state, payment_to_frm_data, frm_connector_details) + .await?; + Ok(FrmInfo { + fraud_check_operation, + frm_data, + suggested_action: None, + }) +} + +#[allow(clippy::too_many_arguments)] +pub async fn pre_payment_frm_core<'a, F>( + state: &AppState, + merchant_account: &domain::MerchantAccount, + payment_data: &mut payments::PaymentData, + frm_info: &mut FrmInfo, + frm_configs: FrmConfigsObject, + customer: &Option, + should_continue_transaction: &mut bool, + key_store: domain::MerchantKeyStore, +) -> RouterResult> +where + F: Send + Clone, +{ + if let Some(frm_data) = &mut frm_info.frm_data { + if matches!( + frm_configs.frm_preferred_flow_type, + api_enums::FrmPreferredFlowTypes::Pre + ) { + let fraud_check_operation = &mut frm_info.fraud_check_operation; + + let frm_router_data = fraud_check_operation + .to_domain()? + .pre_payment_frm( + state, + payment_data, + frm_data, + merchant_account, + customer, + key_store, + ) + .await?; + let frm_data_updated = fraud_check_operation + .to_update_tracker()? + .update_tracker( + &*state.store, + frm_data.clone(), + payment_data, + None, + frm_router_data, + ) + .await?; + let frm_fraud_check = frm_data_updated.fraud_check.clone(); + payment_data.frm_message = Some(frm_fraud_check.clone()); + if matches!(frm_fraud_check.frm_status, FraudCheckStatus::Fraud) + //DontTakeAction + { + *should_continue_transaction = false; + if matches!(frm_configs.frm_action, api_enums::FrmAction::CancelTxn) { + frm_info.suggested_action = Some(FrmSuggestion::FrmCancelTransaction); + } else if matches!(frm_configs.frm_action, api_enums::FrmAction::ManualReview) { + frm_info.suggested_action = Some(FrmSuggestion::FrmManualReview); + } + } + logger::debug!( + "frm_updated_data: {:?} {:?}", + frm_info.fraud_check_operation, + frm_info.suggested_action + ); + Ok(Some(frm_data_updated)) + } else { + Ok(Some(frm_data.to_owned())) + } + } else { + Ok(None) + } +} + +#[allow(clippy::too_many_arguments)] +pub async fn post_payment_frm_core<'a, F>( + state: &AppState, + merchant_account: &domain::MerchantAccount, + payment_data: &mut payments::PaymentData, + frm_info: &mut FrmInfo, + frm_configs: FrmConfigsObject, + customer: &Option, + key_store: domain::MerchantKeyStore, +) -> RouterResult> +where + F: Send + Clone, +{ + if let Some(frm_data) = &mut frm_info.frm_data { + // Allow the Post flow only if the payment is succeeded, + // this logic has to be removed if we are going to call /sale or /transaction after failed transaction + let fraud_check_operation = &mut frm_info.fraud_check_operation; + if payment_data.payment_attempt.status == AttemptStatus::Charged { + let frm_router_data_opt = fraud_check_operation + .to_domain()? + .post_payment_frm( + state, + payment_data, + frm_data, + merchant_account, + customer, + key_store.clone(), + ) + .await?; + if let Some(frm_router_data) = frm_router_data_opt { + let mut frm_data = fraud_check_operation + .to_update_tracker()? + .update_tracker( + &*state.store, + frm_data.to_owned(), + payment_data, + None, + frm_router_data.to_owned(), + ) + .await?; + + payment_data.frm_message = Some(frm_data.fraud_check.clone()); + logger::debug!( + "frm_updated_data: {:?} {:?}", + frm_data, + payment_data.frm_message + ); + let mut frm_suggestion = None; + fraud_check_operation + .to_domain()? + .execute_post_tasks( + state, + &mut frm_data, + merchant_account, + frm_configs, + &mut frm_suggestion, + key_store, + payment_data, + customer, + ) + .await?; + logger::debug!("frm_post_tasks_data: {:?}", frm_data); + let updated_frm_data = fraud_check_operation + .to_update_tracker()? + .update_tracker( + &*state.store, + frm_data.to_owned(), + payment_data, + frm_suggestion, + frm_router_data.to_owned(), + ) + .await?; + return Ok(Some(updated_frm_data)); + } + } + + Ok(Some(frm_data.to_owned())) + } else { + Ok(None) + } +} + +#[allow(clippy::too_many_arguments)] +pub async fn call_frm_before_connector_call<'a, F, Req, Ctx>( + db: &dyn StorageInterface, + operation: &BoxedOperation<'_, F, Req, Ctx>, + merchant_account: &domain::MerchantAccount, + payment_data: &mut payments::PaymentData, + state: &AppState, + frm_info: &mut Option>, + customer: &Option, + should_continue_transaction: &mut bool, + key_store: domain::MerchantKeyStore, +) -> RouterResult> +where + F: Send + Clone, +{ + if is_operation_allowed(operation) { + let (is_frm_enabled, frm_routing_algorithm, frm_connector_label, frm_configs) = + should_call_frm(merchant_account, payment_data, db, key_store.clone()).await?; + if let Some((frm_routing_algorithm_val, profile_id)) = + frm_routing_algorithm.zip(frm_connector_label) + { + if let Some(frm_configs) = frm_configs.clone() { + let mut updated_frm_info = make_frm_data_and_fraud_check_operation( + db, + state, + merchant_account, + payment_data.to_owned(), + frm_routing_algorithm_val, + profile_id, + frm_configs.clone(), + customer, + ) + .await?; + + if is_frm_enabled { + pre_payment_frm_core( + state, + merchant_account, + payment_data, + &mut updated_frm_info, + frm_configs, + customer, + should_continue_transaction, + key_store, + ) + .await?; + } + *frm_info = Some(updated_frm_info); + } + } + logger::debug!("frm_configs: {:?} {:?}", frm_configs, is_frm_enabled); + return Ok(frm_configs); + } + Ok(None) +} + +pub fn is_operation_allowed(operation: &Op) -> bool { + !["PaymentSession", "PaymentApprove", "PaymentReject"] + .contains(&format!("{operation:?}").as_str()) +} + +impl From for PaymentDetails { + fn from(payment_data: PaymentToFrmData) -> Self { + Self { + amount: payment_data.amount.into(), + currency: payment_data.payment_attempt.currency, + payment_method: payment_data.payment_attempt.payment_method, + payment_method_type: payment_data.payment_attempt.payment_method_type, + refund_transaction_id: None, + } + } +} + +#[instrument(skip_all)] +pub async fn frm_fulfillment_core( + state: AppState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + req: frm_core_types::FrmFulfillmentRequest, +) -> RouterResponse { + let db = &*state.clone().store; + let payment_intent = db + .find_payment_intent_by_payment_id_merchant_id( + &req.payment_id.clone(), + &merchant_account.merchant_id, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::PaymentNotFound)?; + match payment_intent.status { + IntentStatus::Succeeded => { + let invalid_request_error = errors::ApiErrorResponse::InvalidRequestData { + message: "no fraud check entry found for this payment_id".to_string(), + }; + let existing_fraud_check = db + .find_fraud_check_by_payment_id_if_present( + req.payment_id.clone(), + merchant_account.merchant_id.clone(), + ) + .await + .change_context(invalid_request_error.to_owned())?; + match existing_fraud_check { + Some(fraud_check) => { + if (matches!(fraud_check.frm_transaction_type, FraudCheckType::PreFrm) + && fraud_check.last_step == FraudCheckLastStep::TransactionOrRecordRefund) + || (matches!(fraud_check.frm_transaction_type, FraudCheckType::PostFrm) + && fraud_check.last_step == FraudCheckLastStep::CheckoutOrSale) + { + Box::pin(make_fulfillment_api_call( + db, + fraud_check, + payment_intent, + state, + merchant_account, + key_store, + req, + )) + .await + } else { + Err(errors::ApiErrorResponse::PreconditionFailed {message:"Frm pre/post flow hasn't terminated yet, so fulfillment cannot be called".to_string(),}.into()) + } + } + None => Err(invalid_request_error.into()), + } + } + _ => Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Fulfillment can be performed only for succeeded payment".to_string(), + } + .into()), + } +} + +#[instrument(skip_all)] +pub async fn make_fulfillment_api_call( + db: &dyn StorageInterface, + fraud_check: FraudCheck, + payment_intent: PaymentIntent, + state: AppState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + req: frm_core_types::FrmFulfillmentRequest, +) -> RouterResponse { + let payment_attempt = db + .find_payment_attempt_by_attempt_id_merchant_id( + &payment_intent.active_attempt.get_id(), + &merchant_account.merchant_id, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::PaymentNotFound)?; + let connector_data = FraudCheckConnectorData::get_connector_by_name(&fraud_check.frm_name)?; + let connector_integration: services::BoxedConnectorIntegration< + '_, + Fulfillment, + frm_types::FraudCheckFulfillmentData, + frm_types::FraudCheckResponseData, + > = connector_data.connector.get_connector_integration(); + let modified_request_for_api_call = FrmFullfillmentSignifydApiRequest::from(req); + let router_data = frm_flows::fulfillment_flow::construct_fulfillment_router_data( + &state, + &payment_intent, + &payment_attempt, + &merchant_account, + &key_store, + "signifyd".to_string(), + modified_request_for_api_call, + ) + .await?; + let response = services::execute_connector_processing_step( + &state, + connector_integration, + &router_data, + payments::CallConnectorAction::Trigger, + None, + ) + .await + .to_payment_failed_response()?; + let fraud_check_copy = fraud_check.clone(); + let fraud_check_update = FraudCheckUpdate::ResponseUpdate { + frm_status: fraud_check.frm_status, + frm_transaction_id: fraud_check.frm_transaction_id, + frm_reason: fraud_check.frm_reason, + frm_score: fraud_check.frm_score, + metadata: fraud_check.metadata, + modified_at: common_utils::date_time::now(), + last_step: FraudCheckLastStep::Fulfillment, + }; + let _updated = db + .update_fraud_check_response_with_attempt_id(fraud_check_copy, fraud_check_update) + .await + .map_err(|error| error.change_context(errors::ApiErrorResponse::PaymentNotFound))?; + let fulfillment_response = + response + .response + .map_err(|err| errors::ApiErrorResponse::ExternalConnectorError { + code: err.code, + message: err.message, + connector: connector_data.connector_name.clone().to_string(), + status_code: err.status_code, + reason: err.reason, + })?; + Ok(services::ApplicationResponse::Json(fulfillment_response)) +} diff --git a/crates/router/src/core/fraud_check/flows.rs b/crates/router/src/core/fraud_check/flows.rs new file mode 100644 index 000000000000..3d4916a372be --- /dev/null +++ b/crates/router/src/core/fraud_check/flows.rs @@ -0,0 +1,36 @@ +pub mod checkout_flow; +pub mod fulfillment_flow; +pub mod record_return; +pub mod sale_flow; +pub mod transaction_flow; + +use async_trait::async_trait; + +use crate::{ + core::{ + errors::RouterResult, + payments::{self, flows::ConstructFlowSpecificData}, + }, + routes::AppState, + services, + types::{ + api::{Connector, FraudCheckConnectorData}, + domain, + fraud_check::FraudCheckResponseData, + }, +}; + +#[async_trait] +pub trait FeatureFrm { + async fn decide_frm_flows<'a>( + self, + state: &AppState, + connector: &FraudCheckConnectorData, + call_connector_action: payments::CallConnectorAction, + merchant_account: &domain::MerchantAccount, + ) -> RouterResult + where + Self: Sized, + F: Clone, + dyn Connector: services::ConnectorIntegration; +} diff --git a/crates/router/src/core/fraud_check/flows/checkout_flow.rs b/crates/router/src/core/fraud_check/flows/checkout_flow.rs new file mode 100644 index 000000000000..47a29d657484 --- /dev/null +++ b/crates/router/src/core/fraud_check/flows/checkout_flow.rs @@ -0,0 +1,147 @@ +use async_trait::async_trait; +use common_utils::ext_traits::ValueExt; +use error_stack::ResultExt; + +use super::{ConstructFlowSpecificData, FeatureFrm}; +use crate::{ + core::{ + errors::{ConnectorErrorExt, RouterResult}, + fraud_check::types::FrmData, + payments::{self, helpers}, + }, + errors, services, + types::{ + api::fraud_check::{self as frm_api, FraudCheckConnectorData}, + domain, + fraud_check::{FraudCheckCheckoutData, FraudCheckResponseData, FrmCheckoutRouterData}, + storage::enums as storage_enums, + ConnectorAuthType, ResponseId, RouterData, + }, + AppState, +}; + +#[async_trait] +impl ConstructFlowSpecificData + for FrmData +{ + async fn construct_router_data<'a>( + &self, + _state: &AppState, + connector_id: &str, + merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, + customer: &Option, + merchant_connector_account: &helpers::MerchantConnectorAccountType, + ) -> RouterResult> + { + let status = storage_enums::AttemptStatus::Pending; + + let auth_type: ConnectorAuthType = merchant_connector_account + .get_connector_account_details() + .parse_value("ConnectorAuthType") + .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: "ConnectorAuthType".to_string(), + })?; + + let customer_id = customer.to_owned().map(|customer| customer.customer_id); + + let router_data = RouterData { + flow: std::marker::PhantomData, + merchant_id: merchant_account.merchant_id.clone(), + customer_id, + connector: connector_id.to_string(), + payment_id: self.payment_intent.payment_id.clone(), + attempt_id: self.payment_attempt.attempt_id.clone(), + status, + payment_method: self + .payment_attempt + .payment_method + .ok_or(errors::ApiErrorResponse::PaymentMethodNotFound)?, + connector_auth_type: auth_type, + description: None, + return_url: None, + payment_method_id: None, + address: self.address.clone(), + auth_type: storage_enums::AuthenticationType::NoThreeDs, + connector_meta_data: None, + amount_captured: None, + request: FraudCheckCheckoutData { + amount: self.payment_attempt.amount, + order_details: self.order_details.clone(), + }, // self.order_details + response: Ok(FraudCheckResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId("".to_string()), + connector_metadata: None, + status: storage_enums::FraudCheckStatus::Pending, + score: None, + reason: None, + }), + access_token: None, + session_token: None, + reference_id: None, + payment_method_token: None, + connector_customer: None, + preprocessing_id: None, + connector_request_reference_id: uuid::Uuid::new_v4().to_string(), + test_mode: None, + recurring_mandate_payment_data: None, + #[cfg(feature = "payouts")] + payout_method_data: None, + #[cfg(feature = "payouts")] + quote_id: None, + payment_method_balance: None, + connector_http_status_code: None, + external_latency: None, + connector_api_version: None, + apple_pay_flow: None, + }; + + Ok(router_data) + } +} + +#[async_trait] +impl FeatureFrm for FrmCheckoutRouterData { + async fn decide_frm_flows<'a>( + mut self, + state: &AppState, + connector: &FraudCheckConnectorData, + call_connector_action: payments::CallConnectorAction, + merchant_account: &domain::MerchantAccount, + ) -> RouterResult { + decide_frm_flow( + &mut self, + state, + connector, + call_connector_action, + merchant_account, + ) + .await + } +} + +pub async fn decide_frm_flow<'a, 'b>( + router_data: &'b mut FrmCheckoutRouterData, + state: &'a AppState, + connector: &FraudCheckConnectorData, + call_connector_action: payments::CallConnectorAction, + _merchant_account: &domain::MerchantAccount, +) -> RouterResult { + let connector_integration: services::BoxedConnectorIntegration< + '_, + frm_api::Checkout, + FraudCheckCheckoutData, + FraudCheckResponseData, + > = connector.connector.get_connector_integration(); + let resp = services::execute_connector_processing_step( + state, + connector_integration, + router_data, + call_connector_action, + None, + ) + .await + .to_payment_failed_response()?; + + Ok(resp) +} diff --git a/crates/router/src/core/fraud_check/flows/fulfillment_flow.rs b/crates/router/src/core/fraud_check/flows/fulfillment_flow.rs new file mode 100644 index 000000000000..6865a9510819 --- /dev/null +++ b/crates/router/src/core/fraud_check/flows/fulfillment_flow.rs @@ -0,0 +1,110 @@ +use common_utils::ext_traits::ValueExt; +use error_stack::ResultExt; +use router_env::tracing::{self, instrument}; + +use crate::{ + connector::signifyd::transformers::FrmFullfillmentSignifydApiRequest, + core::{ + errors::RouterResult, + payments::{helpers, PaymentAddress}, + utils as core_utils, + }, + errors, + types::{ + domain, + fraud_check::{FraudCheckFulfillmentData, FrmFulfillmentRouterData}, + storage, ConnectorAuthType, ErrorResponse, RouterData, + }, + utils, AppState, +}; + +#[instrument(skip_all)] +pub async fn construct_fulfillment_router_data<'a>( + state: &'a AppState, + payment_intent: &'a storage::PaymentIntent, + payment_attempt: &storage::PaymentAttempt, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + connector: String, + fulfillment_request: FrmFullfillmentSignifydApiRequest, +) -> RouterResult { + let profile_id = core_utils::get_profile_id_from_business_details( + payment_intent.business_country, + payment_intent.business_label.as_ref(), + merchant_account, + payment_intent.profile_id.as_ref(), + &*state.store, + false, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("profile_id is not set in payment_intent")?; + + let merchant_connector_account = helpers::get_merchant_connector_account( + state, + merchant_account.merchant_id.as_str(), + None, + key_store, + &profile_id, + &connector, + None, + ) + .await?; + + let test_mode: Option = merchant_connector_account.is_test_mode_on(); + let auth_type: ConnectorAuthType = merchant_connector_account + .get_connector_account_details() + .parse_value("ConnectorAuthType") + .change_context(errors::ApiErrorResponse::InternalServerError)?; + let payment_method = utils::OptionExt::get_required_value( + payment_attempt.payment_method, + "payment_method_type", + )?; + let router_data = RouterData { + flow: std::marker::PhantomData, + merchant_id: merchant_account.merchant_id.clone(), + connector, + payment_id: payment_attempt.payment_id.clone(), + attempt_id: payment_attempt.attempt_id.clone(), + status: payment_attempt.status, + payment_method, + connector_auth_type: auth_type, + description: None, + return_url: payment_intent.return_url.clone(), + payment_method_id: payment_attempt.payment_method_id.clone(), + address: PaymentAddress::default(), + auth_type: payment_attempt.authentication_type.unwrap_or_default(), + connector_meta_data: merchant_connector_account.get_metadata(), + amount_captured: payment_intent.amount_captured, + request: FraudCheckFulfillmentData { + amount: payment_attempt.amount, + order_details: payment_intent.order_details.clone(), + fulfillment_request, + }, + response: Err(ErrorResponse::default()), + access_token: None, + session_token: None, + reference_id: None, + payment_method_token: None, + connector_customer: None, + customer_id: None, + recurring_mandate_payment_data: None, + preprocessing_id: None, + payment_method_balance: None, + connector_request_reference_id: core_utils::get_connector_request_reference_id( + &state.conf, + &merchant_account.merchant_id, + payment_attempt, + ), + #[cfg(feature = "payouts")] + payout_method_data: None, + #[cfg(feature = "payouts")] + quote_id: None, + test_mode, + connector_api_version: None, + connector_http_status_code: None, + external_latency: None, + apple_pay_flow: None, + }; + Ok(router_data) +} diff --git a/crates/router/src/core/fraud_check/flows/record_return.rs b/crates/router/src/core/fraud_check/flows/record_return.rs new file mode 100644 index 000000000000..eaefdbefcc77 --- /dev/null +++ b/crates/router/src/core/fraud_check/flows/record_return.rs @@ -0,0 +1,149 @@ +use async_trait::async_trait; +use common_utils::ext_traits::ValueExt; +use error_stack::ResultExt; + +use crate::{ + connector::signifyd::transformers::RefundMethod, + core::{ + errors::{ConnectorErrorExt, RouterResult}, + fraud_check::{FeatureFrm, FraudCheckConnectorData, FrmData}, + payments::{self, flows::ConstructFlowSpecificData, helpers}, + }, + errors, services, + types::{ + api::RecordReturn, + domain, + fraud_check::{ + FraudCheckRecordReturnData, FraudCheckResponseData, FrmRecordReturnRouterData, + }, + storage::enums as storage_enums, + ConnectorAuthType, ResponseId, RouterData, + }, + utils, AppState, +}; + +#[async_trait] +impl ConstructFlowSpecificData + for FrmData +{ + async fn construct_router_data<'a>( + &self, + _state: &AppState, + connector_id: &str, + merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, + customer: &Option, + merchant_connector_account: &helpers::MerchantConnectorAccountType, + ) -> RouterResult> + { + let status = storage_enums::AttemptStatus::Pending; + + let auth_type: ConnectorAuthType = merchant_connector_account + .get_connector_account_details() + .parse_value("ConnectorAuthType") + .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: "ConnectorAuthType".to_string(), + })?; + + let customer_id = customer.to_owned().map(|customer| customer.customer_id); + let currency = self.payment_attempt.clone().currency; + let router_data = RouterData { + flow: std::marker::PhantomData, + merchant_id: merchant_account.merchant_id.clone(), + customer_id, + connector: connector_id.to_string(), + payment_id: self.payment_intent.payment_id.clone(), + attempt_id: self.payment_attempt.attempt_id.clone(), + status, + payment_method: utils::OptionExt::get_required_value( + self.payment_attempt.payment_method, + "payment_method_type", + )?, + connector_auth_type: auth_type, + description: None, + return_url: None, + payment_method_id: None, + address: self.address.clone(), + auth_type: storage_enums::AuthenticationType::NoThreeDs, + connector_meta_data: None, + amount_captured: None, + request: FraudCheckRecordReturnData { + amount: self.payment_attempt.amount, + refund_method: RefundMethod::OriginalPaymentInstrument, //we dont consume this data now in payments...hence hardcoded + currency, + refund_transaction_id: self.refund.clone().map(|refund| refund.refund_id), + }, // self.order_details + response: Ok(FraudCheckResponseData::RecordReturnResponse { + resource_id: ResponseId::ConnectorTransactionId("".to_string()), + connector_metadata: None, + return_id: None, + }), + access_token: None, + session_token: None, + reference_id: None, + payment_method_token: None, + connector_customer: None, + preprocessing_id: None, + connector_request_reference_id: uuid::Uuid::new_v4().to_string(), + test_mode: None, + recurring_mandate_payment_data: None, + #[cfg(feature = "payouts")] + payout_method_data: None, + #[cfg(feature = "payouts")] + quote_id: None, + payment_method_balance: None, + connector_http_status_code: None, + external_latency: None, + connector_api_version: None, + apple_pay_flow: None, + }; + + Ok(router_data) + } +} + +#[async_trait] +impl FeatureFrm for FrmRecordReturnRouterData { + async fn decide_frm_flows<'a>( + mut self, + state: &AppState, + connector: &FraudCheckConnectorData, + call_connector_action: payments::CallConnectorAction, + merchant_account: &domain::MerchantAccount, + ) -> RouterResult { + decide_frm_flow( + &mut self, + state, + connector, + call_connector_action, + merchant_account, + ) + .await + } +} + +pub async fn decide_frm_flow<'a, 'b>( + router_data: &'b mut FrmRecordReturnRouterData, + state: &'a AppState, + connector: &FraudCheckConnectorData, + call_connector_action: payments::CallConnectorAction, + _merchant_account: &domain::MerchantAccount, +) -> RouterResult { + let connector_integration: services::BoxedConnectorIntegration< + '_, + RecordReturn, + FraudCheckRecordReturnData, + FraudCheckResponseData, + > = connector.connector.get_connector_integration(); + let resp = services::execute_connector_processing_step( + state, + connector_integration, + router_data, + call_connector_action, + None, + ) + .await + .to_payment_failed_response()?; + + Ok(resp) +} diff --git a/crates/router/src/core/fraud_check/flows/sale_flow.rs b/crates/router/src/core/fraud_check/flows/sale_flow.rs new file mode 100644 index 000000000000..c62b096ab374 --- /dev/null +++ b/crates/router/src/core/fraud_check/flows/sale_flow.rs @@ -0,0 +1,145 @@ +use async_trait::async_trait; +use common_utils::ext_traits::ValueExt; +use error_stack::ResultExt; + +use crate::{ + core::{ + errors::{ConnectorErrorExt, RouterResult}, + fraud_check::{FeatureFrm, FraudCheckConnectorData, FrmData}, + payments::{self, flows::ConstructFlowSpecificData, helpers}, + }, + errors, services, + types::{ + api::fraud_check as frm_api, + domain, + fraud_check::{FraudCheckResponseData, FraudCheckSaleData, FrmSaleRouterData}, + storage::enums as storage_enums, + ConnectorAuthType, ResponseId, RouterData, + }, + AppState, +}; + +#[async_trait] +impl ConstructFlowSpecificData + for FrmData +{ + async fn construct_router_data<'a>( + &self, + _state: &AppState, + connector_id: &str, + merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, + customer: &Option, + merchant_connector_account: &helpers::MerchantConnectorAccountType, + ) -> RouterResult> { + let status = storage_enums::AttemptStatus::Pending; + + let auth_type: ConnectorAuthType = merchant_connector_account + .get_connector_account_details() + .parse_value("ConnectorAuthType") + .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: "ConnectorAuthType".to_string(), + })?; + + let customer_id = customer.to_owned().map(|customer| customer.customer_id); + + let router_data = RouterData { + flow: std::marker::PhantomData, + merchant_id: merchant_account.merchant_id.clone(), + customer_id, + connector: connector_id.to_string(), + payment_id: self.payment_intent.payment_id.clone(), + attempt_id: self.payment_attempt.attempt_id.clone(), + status, + payment_method: self + .payment_attempt + .payment_method + .ok_or(errors::ApiErrorResponse::PaymentMethodNotFound)?, + connector_auth_type: auth_type, + description: None, + return_url: None, + payment_method_id: None, + address: self.address.clone(), + auth_type: storage_enums::AuthenticationType::NoThreeDs, + connector_meta_data: None, + amount_captured: None, + request: FraudCheckSaleData { + amount: self.payment_attempt.amount, + order_details: self.order_details.clone(), + }, + response: Ok(FraudCheckResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId("".to_string()), + connector_metadata: None, + status: storage_enums::FraudCheckStatus::Pending, + score: None, + reason: None, + }), + access_token: None, + session_token: None, + reference_id: None, + payment_method_token: None, + connector_customer: None, + preprocessing_id: None, + connector_request_reference_id: uuid::Uuid::new_v4().to_string(), + test_mode: None, + recurring_mandate_payment_data: None, + #[cfg(feature = "payouts")] + payout_method_data: None, + #[cfg(feature = "payouts")] + quote_id: None, + payment_method_balance: None, + connector_http_status_code: None, + external_latency: None, + connector_api_version: None, + apple_pay_flow: None, + }; + + Ok(router_data) + } +} + +#[async_trait] +impl FeatureFrm for FrmSaleRouterData { + async fn decide_frm_flows<'a>( + mut self, + state: &AppState, + connector: &FraudCheckConnectorData, + call_connector_action: payments::CallConnectorAction, + merchant_account: &domain::MerchantAccount, + ) -> RouterResult { + decide_frm_flow( + &mut self, + state, + connector, + call_connector_action, + merchant_account, + ) + .await + } +} + +pub async fn decide_frm_flow<'a, 'b>( + router_data: &'b mut FrmSaleRouterData, + state: &'a AppState, + connector: &FraudCheckConnectorData, + call_connector_action: payments::CallConnectorAction, + _merchant_account: &domain::MerchantAccount, +) -> RouterResult { + let connector_integration: services::BoxedConnectorIntegration< + '_, + frm_api::Sale, + FraudCheckSaleData, + FraudCheckResponseData, + > = connector.connector.get_connector_integration(); + let resp = services::execute_connector_processing_step( + state, + connector_integration, + router_data, + call_connector_action, + None, + ) + .await + .to_payment_failed_response()?; + + Ok(resp) +} diff --git a/crates/router/src/core/fraud_check/flows/transaction_flow.rs b/crates/router/src/core/fraud_check/flows/transaction_flow.rs new file mode 100644 index 000000000000..1c2b8995dfab --- /dev/null +++ b/crates/router/src/core/fraud_check/flows/transaction_flow.rs @@ -0,0 +1,158 @@ +use async_trait::async_trait; +use common_utils::ext_traits::ValueExt; +use error_stack::ResultExt; + +use crate::{ + core::{ + errors::{ConnectorErrorExt, RouterResult}, + fraud_check::{FeatureFrm, FrmData}, + payments::{self, flows::ConstructFlowSpecificData, helpers}, + }, + errors, services, + types::{ + api::fraud_check as frm_api, + domain, + fraud_check::{ + FraudCheckResponseData, FraudCheckTransactionData, FrmTransactionRouterData, + }, + storage::enums as storage_enums, + ConnectorAuthType, ResponseId, RouterData, + }, + AppState, +}; + +#[async_trait] +impl + ConstructFlowSpecificData< + frm_api::Transaction, + FraudCheckTransactionData, + FraudCheckResponseData, + > for FrmData +{ + async fn construct_router_data<'a>( + &self, + _state: &AppState, + connector_id: &str, + merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, + customer: &Option, + merchant_connector_account: &helpers::MerchantConnectorAccountType, + ) -> RouterResult< + RouterData, + > { + let status = storage_enums::AttemptStatus::Pending; + + let auth_type: ConnectorAuthType = merchant_connector_account + .get_connector_account_details() + .parse_value("ConnectorAuthType") + .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: "ConnectorAuthType".to_string(), + })?; + + let customer_id = customer.to_owned().map(|customer| customer.customer_id); + + let payment_method = self.payment_attempt.payment_method; + let currency = self.payment_attempt.currency; + + let router_data = RouterData { + flow: std::marker::PhantomData, + merchant_id: merchant_account.merchant_id.clone(), + customer_id, + connector: connector_id.to_string(), + payment_id: self.payment_intent.payment_id.clone(), + attempt_id: self.payment_attempt.attempt_id.clone(), + status, + payment_method: self + .payment_attempt + .payment_method + .ok_or(errors::ApiErrorResponse::PaymentMethodNotFound)?, + connector_auth_type: auth_type, + description: None, + return_url: None, + payment_method_id: None, + address: self.address.clone(), + auth_type: storage_enums::AuthenticationType::NoThreeDs, + connector_meta_data: None, + amount_captured: None, + request: FraudCheckTransactionData { + amount: self.payment_attempt.amount, + order_details: self.order_details.clone(), + currency, + payment_method, + }, // self.order_details + response: Ok(FraudCheckResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId("".to_string()), + connector_metadata: None, + status: storage_enums::FraudCheckStatus::Pending, + score: None, + reason: None, + }), + access_token: None, + session_token: None, + reference_id: None, + payment_method_token: None, + connector_customer: None, + preprocessing_id: None, + connector_request_reference_id: uuid::Uuid::new_v4().to_string(), + test_mode: None, + recurring_mandate_payment_data: None, + #[cfg(feature = "payouts")] + payout_method_data: None, + #[cfg(feature = "payouts")] + quote_id: None, + payment_method_balance: None, + connector_http_status_code: None, + external_latency: None, + connector_api_version: None, + apple_pay_flow: None, + }; + + Ok(router_data) + } +} + +#[async_trait] +impl FeatureFrm for FrmTransactionRouterData { + async fn decide_frm_flows<'a>( + mut self, + state: &AppState, + connector: &frm_api::FraudCheckConnectorData, + call_connector_action: payments::CallConnectorAction, + merchant_account: &domain::MerchantAccount, + ) -> RouterResult { + decide_frm_flow( + &mut self, + state, + connector, + call_connector_action, + merchant_account, + ) + .await + } +} + +pub async fn decide_frm_flow<'a, 'b>( + router_data: &'b mut FrmTransactionRouterData, + state: &'a AppState, + connector: &frm_api::FraudCheckConnectorData, + call_connector_action: payments::CallConnectorAction, + _merchant_account: &domain::MerchantAccount, +) -> RouterResult { + let connector_integration: services::BoxedConnectorIntegration< + '_, + frm_api::Transaction, + FraudCheckTransactionData, + FraudCheckResponseData, + > = connector.connector.get_connector_integration(); + let resp = services::execute_connector_processing_step( + state, + connector_integration, + router_data, + call_connector_action, + None, + ) + .await + .to_payment_failed_response()?; + + Ok(resp) +} diff --git a/crates/router/src/core/fraud_check/operation.rs b/crates/router/src/core/fraud_check/operation.rs new file mode 100644 index 000000000000..e7677dad6f3a --- /dev/null +++ b/crates/router/src/core/fraud_check/operation.rs @@ -0,0 +1,106 @@ +pub mod fraud_check_post; +pub mod fraud_check_pre; +use async_trait::async_trait; +use common_enums::FrmSuggestion; +use error_stack::{report, ResultExt}; + +pub use self::{fraud_check_post::FraudCheckPost, fraud_check_pre::FraudCheckPre}; +use super::{ + types::{ConnectorDetailsCore, FrmConfigsObject, PaymentToFrmData}, + FrmData, +}; +use crate::{ + core::{ + errors::{self, RouterResult}, + payments, + }, + db::StorageInterface, + routes::AppState, + types::{domain, fraud_check::FrmRouterData}, +}; + +pub type BoxedFraudCheckOperation = Box + Send + Sync>; + +pub trait FraudCheckOperation: Send + std::fmt::Debug { + fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker + Send + Sync)> { + Err(report!(errors::ApiErrorResponse::InternalServerError)) + .attach_printable_lazy(|| format!("get tracker interface not found for {self:?}")) + } + fn to_domain(&self) -> RouterResult<&(dyn Domain)> { + Err(report!(errors::ApiErrorResponse::InternalServerError)) + .attach_printable_lazy(|| format!("domain interface not found for {self:?}")) + } + fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { + Err(report!(errors::ApiErrorResponse::InternalServerError)) + .attach_printable_lazy(|| format!("get tracker interface not found for {self:?}")) + } +} + +#[async_trait] +pub trait GetTracker: Send { + async fn get_trackers<'a>( + &'a self, + state: &'a AppState, + payment_data: D, + frm_connector_details: ConnectorDetailsCore, + ) -> RouterResult>; +} + +#[async_trait] +pub trait Domain: Send + Sync { + async fn post_payment_frm<'a>( + &'a self, + state: &'a AppState, + payment_data: &mut payments::PaymentData, + frm_data: &mut FrmData, + merchant_account: &domain::MerchantAccount, + customer: &Option, + key_store: domain::MerchantKeyStore, + ) -> RouterResult> + where + F: Send + Clone; + + async fn pre_payment_frm<'a>( + &'a self, + state: &'a AppState, + payment_data: &mut payments::PaymentData, + frm_data: &mut FrmData, + merchant_account: &domain::MerchantAccount, + customer: &Option, + key_store: domain::MerchantKeyStore, + ) -> RouterResult + where + F: Send + Clone; + + // To execute several tasks conditionally based on the result of post_flow. + // Eg: If the /sale(post flow) is returning the transaction as fraud we can execute refund in post task + #[allow(clippy::too_many_arguments)] + async fn execute_post_tasks( + &self, + _state: &AppState, + frm_data: &mut FrmData, + _merchant_account: &domain::MerchantAccount, + _frm_configs: FrmConfigsObject, + _frm_suggestion: &mut Option, + _key_store: domain::MerchantKeyStore, + _payment_data: &mut payments::PaymentData, + _customer: &Option, + ) -> RouterResult> + where + F: Send + Clone, + { + return Ok(Some(frm_data.to_owned())); + } +} + +#[async_trait] +pub trait UpdateTracker: Send { + async fn update_tracker<'b>( + &'b self, + db: &dyn StorageInterface, + frm_data: D, + payment_data: &mut payments::PaymentData, + _frm_suggestion: Option, + frm_router_data: FrmRouterData, + ) -> RouterResult; +} diff --git a/crates/router/src/core/fraud_check/operation/fraud_check_post.rs b/crates/router/src/core/fraud_check/operation/fraud_check_post.rs new file mode 100644 index 000000000000..37838ddaab5a --- /dev/null +++ b/crates/router/src/core/fraud_check/operation/fraud_check_post.rs @@ -0,0 +1,457 @@ +use async_trait::async_trait; +use common_enums::FrmSuggestion; +use common_utils::ext_traits::Encode; +use data_models::payments::{ + payment_attempt::PaymentAttemptUpdate, payment_intent::PaymentIntentUpdate, +}; +use router_env::{instrument, logger, tracing}; + +use super::{Domain, FraudCheckOperation, GetTracker, UpdateTracker}; +use crate::{ + consts, + core::{ + errors::{RouterResult, StorageErrorExt}, + fraud_check::{ + self as frm_core, + types::{FrmData, PaymentDetails, PaymentToFrmData, REFUND_INITIATED}, + ConnectorDetailsCore, FrmConfigsObject, + }, + payments, refunds, + }, + db::StorageInterface, + errors, services, + types::{ + api::{ + enums::{AttemptStatus, FrmAction, IntentStatus}, + fraud_check as frm_api, + refunds::{RefundRequest, RefundType}, + }, + domain, + fraud_check::{ + FraudCheckResponseData, FraudCheckSaleData, FrmRequest, FrmResponse, FrmRouterData, + }, + storage::{ + enums::{FraudCheckLastStep, FraudCheckStatus, FraudCheckType, MerchantDecision}, + fraud_check::{FraudCheckNew, FraudCheckUpdate}, + }, + ResponseId, + }, + utils, AppState, +}; + +#[derive(Debug, Clone, Copy)] +pub struct FraudCheckPost; + +impl FraudCheckOperation for &FraudCheckPost { + fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker + Send + Sync)> { + Ok(*self) + } + fn to_domain(&self) -> RouterResult<&(dyn Domain)> { + Ok(*self) + } + fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { + Ok(*self) + } +} + +impl FraudCheckOperation for FraudCheckPost { + fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker + Send + Sync)> { + Ok(self) + } + fn to_domain(&self) -> RouterResult<&(dyn Domain)> { + Ok(self) + } + fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { + Ok(self) + } +} + +#[async_trait] +impl GetTracker for FraudCheckPost { + #[instrument(skip_all)] + async fn get_trackers<'a>( + &'a self, + state: &'a AppState, + payment_data: PaymentToFrmData, + frm_connector_details: ConnectorDetailsCore, + ) -> RouterResult> { + let db = &*state.store; + + let payment_details: Option = + Encode::::encode_to_value(&PaymentDetails::from(payment_data.clone())) + .ok(); + let existing_fraud_check = db + .find_fraud_check_by_payment_id_if_present( + payment_data.payment_intent.payment_id.clone(), + payment_data.merchant_account.merchant_id.clone(), + ) + .await + .ok(); + let fraud_check = match existing_fraud_check { + Some(Some(fraud_check)) => Ok(fraud_check), + _ => { + db.insert_fraud_check_response(FraudCheckNew { + frm_id: utils::generate_id(consts::ID_LENGTH, "frm"), + payment_id: payment_data.payment_intent.payment_id.clone(), + merchant_id: payment_data.merchant_account.merchant_id.clone(), + attempt_id: payment_data.payment_attempt.attempt_id.clone(), + created_at: common_utils::date_time::now(), + frm_name: frm_connector_details.connector_name, + frm_transaction_id: None, + frm_transaction_type: FraudCheckType::PostFrm, + frm_status: FraudCheckStatus::Pending, + frm_score: None, + frm_reason: None, + frm_error: None, + payment_details, + metadata: None, + modified_at: common_utils::date_time::now(), + last_step: FraudCheckLastStep::Processing, + }) + .await + } + }; + match fraud_check { + Ok(fraud_check_value) => { + let frm_data = FrmData { + payment_intent: payment_data.payment_intent, + payment_attempt: payment_data.payment_attempt, + merchant_account: payment_data.merchant_account, + address: payment_data.address, + fraud_check: fraud_check_value, + connector_details: payment_data.connector_details, + order_details: payment_data.order_details, + refund: None, + }; + Ok(Some(frm_data)) + } + Err(error) => { + router_env::logger::error!("inserting into fraud_check table failed {error:?}"); + Ok(None) + } + } + } +} + +#[async_trait] +impl Domain for FraudCheckPost { + #[instrument(skip_all)] + async fn post_payment_frm<'a>( + &'a self, + state: &'a AppState, + payment_data: &mut payments::PaymentData, + frm_data: &mut FrmData, + merchant_account: &domain::MerchantAccount, + customer: &Option, + key_store: domain::MerchantKeyStore, + ) -> RouterResult> { + if frm_data.fraud_check.last_step != FraudCheckLastStep::Processing { + logger::debug!("post_flow::Sale Skipped"); + return Ok(None); + } + let router_data = frm_core::call_frm_service::( + state, + payment_data, + frm_data.to_owned(), + merchant_account, + &key_store, + customer, + ) + .await?; + frm_data.fraud_check.last_step = FraudCheckLastStep::CheckoutOrSale; + Ok(Some(FrmRouterData { + merchant_id: router_data.merchant_id, + connector: router_data.connector, + payment_id: router_data.payment_id, + attempt_id: router_data.attempt_id, + request: FrmRequest::Sale(FraudCheckSaleData { + amount: router_data.request.amount, + order_details: router_data.request.order_details, + }), + response: FrmResponse::Sale(router_data.response), + })) + } + + #[instrument(skip_all)] + async fn execute_post_tasks( + &self, + state: &AppState, + frm_data: &mut FrmData, + merchant_account: &domain::MerchantAccount, + frm_configs: FrmConfigsObject, + frm_suggestion: &mut Option, + key_store: domain::MerchantKeyStore, + payment_data: &mut payments::PaymentData, + customer: &Option, + ) -> RouterResult> { + if matches!(frm_data.fraud_check.frm_status, FraudCheckStatus::Fraud) + && matches!(frm_configs.frm_action, FrmAction::AutoRefund) + && matches!( + frm_data.fraud_check.last_step, + FraudCheckLastStep::CheckoutOrSale + ) + { + *frm_suggestion = Some(FrmSuggestion::FrmAutoRefund); + let ref_req = RefundRequest { + refund_id: None, + payment_id: payment_data.payment_intent.payment_id.clone(), + merchant_id: Some(merchant_account.merchant_id.clone()), + amount: None, + reason: frm_data + .fraud_check + .frm_reason + .clone() + .map(|data| data.to_string()), + refund_type: Some(RefundType::Instant), + metadata: None, + merchant_connector_details: None, + }; + let refund = Box::pin(refunds::refund_create_core( + state.clone(), + merchant_account.clone(), + key_store.clone(), + ref_req, + )) + .await?; + if let services::ApplicationResponse::Json(new_refund) = refund { + frm_data.refund = Some(new_refund); + } + let _router_data = frm_core::call_frm_service::( + state, + payment_data, + frm_data.to_owned(), + merchant_account, + &key_store, + customer, + ) + .await?; + frm_data.fraud_check.last_step = FraudCheckLastStep::TransactionOrRecordRefund; + }; + return Ok(Some(frm_data.to_owned())); + } + + #[instrument(skip_all)] + async fn pre_payment_frm<'a>( + &'a self, + state: &'a AppState, + payment_data: &mut payments::PaymentData, + frm_data: &mut FrmData, + merchant_account: &domain::MerchantAccount, + customer: &Option, + key_store: domain::MerchantKeyStore, + ) -> RouterResult { + let router_data = frm_core::call_frm_service::( + state, + payment_data, + frm_data.to_owned(), + merchant_account, + &key_store, + customer, + ) + .await?; + Ok(FrmRouterData { + merchant_id: router_data.merchant_id, + connector: router_data.connector, + payment_id: router_data.payment_id, + attempt_id: router_data.attempt_id, + request: FrmRequest::Sale(FraudCheckSaleData { + amount: router_data.request.amount, + order_details: router_data.request.order_details, + }), + response: FrmResponse::Sale(router_data.response), + }) + } +} + +#[async_trait] +impl UpdateTracker for FraudCheckPost { + async fn update_tracker<'b>( + &'b self, + db: &dyn StorageInterface, + mut frm_data: FrmData, + payment_data: &mut payments::PaymentData, + frm_suggestion: Option, + frm_router_data: FrmRouterData, + ) -> RouterResult { + let frm_check_update = match frm_router_data.response { + FrmResponse::Sale(response) => match response { + Err(err) => Some(FraudCheckUpdate::ErrorUpdate { + status: FraudCheckStatus::TransactionFailure, + error_message: Some(Some(err.message)), + }), + Ok(payments_response) => match payments_response { + FraudCheckResponseData::TransactionResponse { + resource_id, + connector_metadata, + status, + reason, + score, + } => { + let connector_transaction_id = match resource_id { + ResponseId::NoResponseId => None, + ResponseId::ConnectorTransactionId(id) => Some(id), + ResponseId::EncodedData(id) => Some(id), + }; + + let fraud_check_update = FraudCheckUpdate::ResponseUpdate { + frm_status: status, + frm_transaction_id: connector_transaction_id, + frm_reason: reason, + frm_score: score, + metadata: connector_metadata, + modified_at: common_utils::date_time::now(), + last_step: frm_data.fraud_check.last_step, + }; + Some(fraud_check_update) + }, + FraudCheckResponseData::RecordReturnResponse { resource_id: _, connector_metadata: _, return_id: _ } => { + Some(FraudCheckUpdate::ErrorUpdate { + status: FraudCheckStatus::TransactionFailure, + error_message: Some(Some( + "Error: Got Record Return Response response in current Sale flow".to_string(), + )), + }) + } + FraudCheckResponseData::FulfillmentResponse { + order_id: _, + shipment_ids: _, + } => None, + }, + }, + FrmResponse::Fulfillment(response) => match response { + Err(err) => Some(FraudCheckUpdate::ErrorUpdate { + status: FraudCheckStatus::TransactionFailure, + error_message: Some(Some(err.message)), + }), + Ok(payments_response) => match payments_response { + FraudCheckResponseData::TransactionResponse { + resource_id, + connector_metadata, + status, + reason, + score, + } => { + let connector_transaction_id = match resource_id { + ResponseId::NoResponseId => None, + ResponseId::ConnectorTransactionId(id) => Some(id), + ResponseId::EncodedData(id) => Some(id), + }; + + let fraud_check_update = FraudCheckUpdate::ResponseUpdate { + frm_status: status, + frm_transaction_id: connector_transaction_id, + frm_reason: reason, + frm_score: score, + metadata: connector_metadata, + modified_at: common_utils::date_time::now(), + last_step: frm_data.fraud_check.last_step, + }; + Some(fraud_check_update) + } + FraudCheckResponseData::FulfillmentResponse { + order_id: _, + shipment_ids: _, + } => None, + FraudCheckResponseData::RecordReturnResponse { resource_id: _, connector_metadata: _, return_id: _ } => None, + + }, + }, + + FrmResponse::RecordReturn(response) => match response { + Err(err) => Some(FraudCheckUpdate::ErrorUpdate { + status: FraudCheckStatus::TransactionFailure, + error_message: Some(Some(err.message)), + }), + Ok(payments_response) => match payments_response { + FraudCheckResponseData::TransactionResponse { + resource_id: _, + connector_metadata: _, + status: _, + reason: _, + score: _, + } => { + Some(FraudCheckUpdate::ErrorUpdate { + status: FraudCheckStatus::TransactionFailure, + error_message: Some(Some( + "Error: Got Transaction Response response in current Record Return flow".to_string(), + )), + }) + }, + FraudCheckResponseData::FulfillmentResponse {order_id: _, shipment_ids: _ } => { + None + }, + FraudCheckResponseData::RecordReturnResponse { resource_id, connector_metadata, return_id: _ } => { + let connector_transaction_id = match resource_id { + ResponseId::NoResponseId => None, + ResponseId::ConnectorTransactionId(id) => Some(id), + ResponseId::EncodedData(id) => Some(id), + }; + + let fraud_check_update = FraudCheckUpdate::ResponseUpdate { + frm_status: frm_data.fraud_check.frm_status, + frm_transaction_id: connector_transaction_id, + frm_reason: frm_data.fraud_check.frm_reason.clone(), + frm_score: frm_data.fraud_check.frm_score, + metadata: connector_metadata, + modified_at: common_utils::date_time::now(), + last_step: frm_data.fraud_check.last_step, + }; + Some(fraud_check_update) + + } + }, + }, + + + FrmResponse::Checkout(_) | FrmResponse::Transaction(_) => { + Some(FraudCheckUpdate::ErrorUpdate { + status: FraudCheckStatus::TransactionFailure, + error_message: Some(Some( + "Error: Got Pre(Sale) flow response in current post flow".to_string(), + )), + }) + } + }; + + if frm_suggestion == Some(FrmSuggestion::FrmAutoRefund) { + payment_data.payment_attempt = db + .update_payment_attempt_with_attempt_id( + payment_data.payment_attempt.clone(), + PaymentAttemptUpdate::RejectUpdate { + status: AttemptStatus::Failure, + error_code: Some(Some(frm_data.fraud_check.frm_status.to_string())), + error_message: Some(Some(REFUND_INITIATED.to_string())), + updated_by: frm_data.merchant_account.storage_scheme.to_string(), // merchant_decision: Some(MerchantDecision::AutoRefunded), + }, + frm_data.merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + payment_data.payment_intent = db + .update_payment_intent( + payment_data.payment_intent.clone(), + PaymentIntentUpdate::RejectUpdate { + status: IntentStatus::Failed, + merchant_decision: Some(MerchantDecision::AutoRefunded.to_string()), + updated_by: frm_data.merchant_account.storage_scheme.to_string(), + }, + frm_data.merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + } + frm_data.fraud_check = match frm_check_update { + Some(fraud_check_update) => db + .update_fraud_check_response_with_attempt_id( + frm_data.fraud_check.clone(), + fraud_check_update, + ) + .await + .map_err(|error| error.change_context(errors::ApiErrorResponse::PaymentNotFound))?, + None => frm_data.fraud_check.clone(), + }; + + Ok(frm_data) + } +} diff --git a/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs b/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs new file mode 100644 index 000000000000..00f50d01a862 --- /dev/null +++ b/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs @@ -0,0 +1,337 @@ +use async_trait::async_trait; +use common_enums::FrmSuggestion; +use common_utils::ext_traits::Encode; +use diesel_models::enums::FraudCheckLastStep; +use router_env::{instrument, tracing}; +use uuid::Uuid; + +use super::{Domain, FraudCheckOperation, GetTracker, UpdateTracker}; +use crate::{ + core::{ + errors::RouterResult, + fraud_check::{ + self as frm_core, + types::{FrmData, PaymentDetails, PaymentToFrmData}, + ConnectorDetailsCore, + }, + payments, + }, + db::StorageInterface, + errors, + types::{ + api::fraud_check as frm_api, + domain, + fraud_check::{ + FraudCheckCheckoutData, FraudCheckResponseData, FraudCheckTransactionData, FrmRequest, + FrmResponse, FrmRouterData, + }, + storage::{ + enums::{FraudCheckStatus, FraudCheckType}, + fraud_check::{FraudCheckNew, FraudCheckUpdate}, + }, + ResponseId, + }, + AppState, +}; + +#[derive(Debug, Clone, Copy)] +pub struct FraudCheckPre; + +impl FraudCheckOperation for &FraudCheckPre { + fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker + Send + Sync)> { + Ok(*self) + } + fn to_domain(&self) -> RouterResult<&(dyn Domain)> { + Ok(*self) + } + fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { + Ok(*self) + } +} + +impl FraudCheckOperation for FraudCheckPre { + fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker + Send + Sync)> { + Ok(self) + } + fn to_domain(&self) -> RouterResult<&(dyn Domain)> { + Ok(self) + } + fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { + Ok(self) + } +} + +#[async_trait] +impl GetTracker for FraudCheckPre { + #[instrument(skip_all)] + async fn get_trackers<'a>( + &'a self, + state: &'a AppState, + payment_data: PaymentToFrmData, + frm_connector_details: ConnectorDetailsCore, + ) -> RouterResult> { + let db = &*state.store; + + let payment_details: Option = + Encode::::encode_to_value(&PaymentDetails::from(payment_data.clone())) + .ok(); + + let existing_fraud_check = db + .find_fraud_check_by_payment_id_if_present( + payment_data.payment_intent.payment_id.clone(), + payment_data.merchant_account.merchant_id.clone(), + ) + .await + .ok(); + + let fraud_check = match existing_fraud_check { + Some(Some(fraud_check)) => Ok(fraud_check), + _ => { + db.insert_fraud_check_response(FraudCheckNew { + frm_id: Uuid::new_v4().simple().to_string(), + payment_id: payment_data.payment_intent.payment_id.clone(), + merchant_id: payment_data.merchant_account.merchant_id.clone(), + attempt_id: payment_data.payment_attempt.attempt_id.clone(), + created_at: common_utils::date_time::now(), + frm_name: frm_connector_details.connector_name, + frm_transaction_id: None, + frm_transaction_type: FraudCheckType::PreFrm, + frm_status: FraudCheckStatus::Pending, + frm_score: None, + frm_reason: None, + frm_error: None, + payment_details, + metadata: None, + modified_at: common_utils::date_time::now(), + last_step: FraudCheckLastStep::Processing, + }) + .await + } + }; + + match fraud_check { + Ok(fraud_check_value) => { + let frm_data = FrmData { + payment_intent: payment_data.payment_intent, + payment_attempt: payment_data.payment_attempt, + merchant_account: payment_data.merchant_account, + address: payment_data.address, + fraud_check: fraud_check_value, + connector_details: payment_data.connector_details, + order_details: payment_data.order_details, + refund: None, + }; + Ok(Some(frm_data)) + } + Err(error) => { + router_env::logger::error!("inserting into fraud_check table failed {error:?}"); + Ok(None) + } + } + } +} + +#[async_trait] +impl Domain for FraudCheckPre { + #[instrument(skip_all)] + async fn post_payment_frm<'a>( + &'a self, + state: &'a AppState, + payment_data: &mut payments::PaymentData, + frm_data: &mut FrmData, + merchant_account: &domain::MerchantAccount, + customer: &Option, + key_store: domain::MerchantKeyStore, + ) -> RouterResult> { + let router_data = frm_core::call_frm_service::( + state, + payment_data, + frm_data.to_owned(), + merchant_account, + &key_store, + customer, + ) + .await?; + frm_data.fraud_check.last_step = FraudCheckLastStep::TransactionOrRecordRefund; + Ok(Some(FrmRouterData { + merchant_id: router_data.merchant_id, + connector: router_data.connector, + payment_id: router_data.payment_id, + attempt_id: router_data.attempt_id, + request: FrmRequest::Transaction(FraudCheckTransactionData { + amount: router_data.request.amount, + order_details: router_data.request.order_details, + currency: router_data.request.currency, + payment_method: Some(router_data.payment_method), + }), + response: FrmResponse::Transaction(router_data.response), + })) + } + + async fn pre_payment_frm<'a>( + &'a self, + state: &'a AppState, + payment_data: &mut payments::PaymentData, + frm_data: &mut FrmData, + merchant_account: &domain::MerchantAccount, + customer: &Option, + key_store: domain::MerchantKeyStore, + ) -> RouterResult { + let router_data = frm_core::call_frm_service::( + state, + payment_data, + frm_data.to_owned(), + merchant_account, + &key_store, + customer, + ) + .await?; + frm_data.fraud_check.last_step = FraudCheckLastStep::CheckoutOrSale; + Ok(FrmRouterData { + merchant_id: router_data.merchant_id, + connector: router_data.connector, + payment_id: router_data.payment_id, + attempt_id: router_data.attempt_id, + request: FrmRequest::Checkout(FraudCheckCheckoutData { + amount: router_data.request.amount, + order_details: router_data.request.order_details, + }), + response: FrmResponse::Checkout(router_data.response), + }) + } +} + +#[async_trait] +impl UpdateTracker for FraudCheckPre { + async fn update_tracker<'b>( + &'b self, + db: &dyn StorageInterface, + mut frm_data: FrmData, + payment_data: &mut payments::PaymentData, + _frm_suggestion: Option, + frm_router_data: FrmRouterData, + ) -> RouterResult { + let frm_check_update = match frm_router_data.response { + FrmResponse::Checkout(response) => match response { + Err(err) => Some(FraudCheckUpdate::ErrorUpdate { + status: FraudCheckStatus::TransactionFailure, + error_message: Some(Some(err.message)), + }), + Ok(payments_response) => match payments_response { + FraudCheckResponseData::TransactionResponse { + resource_id, + connector_metadata, + status, + reason, + score, + } => { + let connector_transaction_id = match resource_id { + ResponseId::NoResponseId => None, + ResponseId::ConnectorTransactionId(id) => Some(id), + ResponseId::EncodedData(id) => Some(id), + }; + + let fraud_check_update = FraudCheckUpdate::ResponseUpdate { + frm_status: status, + frm_transaction_id: connector_transaction_id, + frm_reason: reason, + frm_score: score, + metadata: connector_metadata, + modified_at: common_utils::date_time::now(), + last_step: frm_data.fraud_check.last_step, + }; + Some(fraud_check_update) + } + FraudCheckResponseData::FulfillmentResponse { + order_id: _, + shipment_ids: _, + } => None, + FraudCheckResponseData::RecordReturnResponse { + resource_id: _, + connector_metadata: _, + return_id: _, + } => Some(FraudCheckUpdate::ErrorUpdate { + status: FraudCheckStatus::TransactionFailure, + error_message: Some(Some( + "Error: Got Record Return Response response in current Checkout flow" + .to_string(), + )), + }), + }, + }, + FrmResponse::Transaction(response) => match response { + Err(err) => Some(FraudCheckUpdate::ErrorUpdate { + status: FraudCheckStatus::TransactionFailure, + error_message: Some(Some(err.message)), + }), + Ok(payments_response) => match payments_response { + FraudCheckResponseData::TransactionResponse { + resource_id, + connector_metadata, + status, + reason, + score, + } => { + let connector_transaction_id = match resource_id { + ResponseId::NoResponseId => None, + ResponseId::ConnectorTransactionId(id) => Some(id), + ResponseId::EncodedData(id) => Some(id), + }; + + let frm_status = payment_data + .frm_message + .as_ref() + .map_or(status, |frm_data| frm_data.frm_status); + + let fraud_check_update = FraudCheckUpdate::ResponseUpdate { + frm_status, + frm_transaction_id: connector_transaction_id, + frm_reason: reason, + frm_score: score, + metadata: connector_metadata, + modified_at: common_utils::date_time::now(), + last_step: frm_data.fraud_check.last_step, + }; + Some(fraud_check_update) + } + FraudCheckResponseData::FulfillmentResponse { + order_id: _, + shipment_ids: _, + } => None, + FraudCheckResponseData::RecordReturnResponse { + resource_id: _, + connector_metadata: _, + return_id: _, + } => Some(FraudCheckUpdate::ErrorUpdate { + status: FraudCheckStatus::TransactionFailure, + error_message: Some(Some( + "Error: Got Record Return Response response in current Checkout flow" + .to_string(), + )), + }), + }, + }, + FrmResponse::Sale(_response) + | FrmResponse::Fulfillment(_response) + | FrmResponse::RecordReturn(_response) => Some(FraudCheckUpdate::ErrorUpdate { + status: FraudCheckStatus::TransactionFailure, + error_message: Some(Some( + "Error: Got Pre(Sale) flow response in current post flow".to_string(), + )), + }), + }; + + frm_data.fraud_check = match frm_check_update { + Some(fraud_check_update) => db + .update_fraud_check_response_with_attempt_id( + frm_data.clone().fraud_check, + fraud_check_update, + ) + .await + .map_err(|error| error.change_context(errors::ApiErrorResponse::PaymentNotFound))?, + None => frm_data.clone().fraud_check, + }; + + Ok(frm_data) + } +} diff --git a/crates/router/src/core/fraud_check/types.rs b/crates/router/src/core/fraud_check/types.rs new file mode 100644 index 000000000000..1d6e7cb45a58 --- /dev/null +++ b/crates/router/src/core/fraud_check/types.rs @@ -0,0 +1,208 @@ +use api_models::{ + enums as api_enums, + enums::{PaymentMethod, PaymentMethodType}, + payments::Amount, + refunds::RefundResponse, +}; +use common_enums::FrmSuggestion; +use common_utils::pii::Email; +use data_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent}; +use masking::Serialize; +use serde::Deserialize; +use utoipa::ToSchema; + +use super::operation::BoxedFraudCheckOperation; +use crate::{ + pii::Secret, + types::{ + domain::MerchantAccount, + storage::{enums as storage_enums, fraud_check::FraudCheck}, + PaymentAddress, + }, +}; + +#[derive(Clone, Default, Debug)] +pub struct PaymentIntentCore { + pub payment_id: String, +} + +#[derive(Clone, Debug)] +pub struct PaymentAttemptCore { + pub attempt_id: String, + pub payment_details: Option, + pub amount: Amount, +} + +#[derive(Clone, Debug, Serialize)] +pub struct PaymentDetails { + pub amount: i64, + pub currency: Option, + pub payment_method: Option, + pub payment_method_type: Option, + pub refund_transaction_id: Option, +} +#[derive(Clone, Default, Debug)] +pub struct FrmMerchantAccount { + pub merchant_id: String, +} + +#[derive(Clone, Debug)] +pub struct FrmData { + pub payment_intent: PaymentIntent, + pub payment_attempt: PaymentAttempt, + pub merchant_account: MerchantAccount, + pub fraud_check: FraudCheck, + pub address: PaymentAddress, + pub connector_details: ConnectorDetailsCore, + pub order_details: Option>, + pub refund: Option, +} + +#[derive(Debug)] +pub struct FrmInfo { + pub fraud_check_operation: BoxedFraudCheckOperation, + pub frm_data: Option, + pub suggested_action: Option, +} + +#[derive(Clone, Debug)] +pub struct ConnectorDetailsCore { + pub connector_name: String, + pub profile_id: String, +} +#[derive(Clone)] +pub struct PaymentToFrmData { + pub amount: Amount, + pub payment_intent: PaymentIntent, + pub payment_attempt: PaymentAttempt, + pub merchant_account: MerchantAccount, + pub address: PaymentAddress, + pub connector_details: ConnectorDetailsCore, + pub order_details: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FrmConfigsObject { + pub frm_enabled_pm: Option, + pub frm_enabled_pm_type: Option, + pub frm_enabled_gateway: Option, + pub frm_action: api_enums::FrmAction, + pub frm_preferred_flow_type: api_enums::FrmPreferredFlowTypes, +} + +#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "camelCase")] +pub struct FrmFulfillmentSignifydApiRequest { + ///unique order_id for the order_details in the transaction + #[schema(max_length = 255, example = "pay_qiYfHcDou1ycIaxVXKHF")] + pub order_id: String, + ///denotes the status of the fulfillment... can be one of PARTIAL, COMPLETE, REPLACEMENT, CANCELED + #[schema(value_type = Option, example = "COMPLETE")] + pub fulfillment_status: Option, + ///contains details of the fulfillment + #[schema(value_type = Vec)] + pub fulfillments: Vec, +} + +#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub struct FrmFulfillmentRequest { + ///unique payment_id for the transaction + #[schema(max_length = 255, example = "pay_qiYfHcDou1ycIaxVXKHF")] + pub payment_id: String, + ///unique order_id for the order_details in the transaction + #[schema(max_length = 255, example = "pay_qiYfHcDou1ycIaxVXKHF")] + pub order_id: String, + ///denotes the status of the fulfillment... can be one of PARTIAL, COMPLETE, REPLACEMENT, CANCELED + #[schema(value_type = Option, example = "COMPLETE")] + pub fulfillment_status: Option, + ///contains details of the fulfillment + #[schema(value_type = Vec)] + pub fulfillments: Vec, +} + +#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub struct Fulfillments { + ///shipment_id of the shipped items + #[schema(max_length = 255, example = "ship_101")] + pub shipment_id: String, + ///products sent in the shipment + #[schema(value_type = Option>)] + pub products: Option>, + ///destination address of the shipment + #[schema(value_type = Destination)] + pub destination: Destination, +} + +#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] +#[serde(untagged)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub enum FulfillmentStatus { + PARTIAL, + COMPLETE, + REPLACEMENT, + CANCELED, +} + +#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub struct Product { + pub item_name: String, + pub item_quantity: i64, + pub item_id: String, +} + +#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub struct Destination { + pub full_name: Secret, + pub organization: Option, + pub email: Option, + pub address: Address, +} + +#[derive(Debug, Serialize, Eq, PartialEq, Deserialize, Clone)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub struct Address { + pub street_address: Secret, + pub unit: Option>, + pub postal_code: Secret, + pub city: String, + pub province_code: Secret, + pub country_code: common_enums::CountryAlpha2, +} + +#[derive(Debug, ToSchema, Clone, Serialize)] +pub struct FrmFulfillmentResponse { + ///unique order_id for the transaction + #[schema(max_length = 255, example = "pay_qiYfHcDou1ycIaxVXKHF")] + pub order_id: String, + ///shipment_ids used in the fulfillment overall...also data from previous fulfillments for the same transactions/order is sent + #[schema(example = r#"["ship_101", "ship_102"]"#)] + pub shipment_ids: Vec, +} + +#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "camelCase")] +pub struct FrmFulfillmentSignifydApiResponse { + ///unique order_id for the transaction + #[schema(max_length = 255, example = "pay_qiYfHcDou1ycIaxVXKHF")] + pub order_id: String, + ///shipment_ids used in the fulfillment overall...also data from previous fulfillments for the same transactions/order is sent + #[schema(example = r#"["ship_101","ship_102"]"#)] + pub shipment_ids: Vec, +} + +pub const REFUND_INITIATED: &str = "Refund Initiated with the processor"; diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 1049137a9470..14a39f1d9556 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -11,12 +11,12 @@ pub use api_models::{ pub use common_utils::request::RequestBody; use data_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent}; use diesel_models::enums; -use error_stack::IntoReport; use crate::{ core::{ - errors::{self, RouterResult}, + errors::RouterResult, payments::helpers, + pm_auth::{self as core_pm_auth}, }, routes::AppState, types::{ @@ -42,7 +42,6 @@ pub trait PaymentMethodRetrieve { key_store: &domain::MerchantKeyStore, token: &storage::PaymentTokenData, payment_intent: &PaymentIntent, - card_cvc: Option>, card_token_data: Option<&CardToken>, ) -> RouterResult>; } @@ -126,7 +125,6 @@ impl PaymentMethodRetrieve for Oss { merchant_key_store: &domain::MerchantKeyStore, token_data: &storage::PaymentTokenData, payment_intent: &PaymentIntent, - card_cvc: Option>, card_token_data: Option<&CardToken>, ) -> RouterResult> { match token_data { @@ -135,7 +133,6 @@ impl PaymentMethodRetrieve for Oss { state, &generic_token.token, payment_intent, - card_cvc, merchant_key_store, card_token_data, ) @@ -147,7 +144,6 @@ impl PaymentMethodRetrieve for Oss { state, &generic_token.token, payment_intent, - card_cvc, merchant_key_store, card_token_data, ) @@ -159,7 +155,6 @@ impl PaymentMethodRetrieve for Oss { state, &card_token.token, payment_intent, - card_cvc, card_token_data, ) .await @@ -171,18 +166,20 @@ impl PaymentMethodRetrieve for Oss { state, &card_token.token, payment_intent, - card_cvc, card_token_data, ) .await .map(|card| Some((card, enums::PaymentMethod::Card))) } - storage::PaymentTokenData::AuthBankDebit(_) => { - Err(errors::ApiErrorResponse::NotImplemented { - message: errors::NotImplementedMessage::Default, - }) - .into_report() + storage::PaymentTokenData::AuthBankDebit(auth_token) => { + core_pm_auth::retrieve_payment_method_from_auth_service( + state, + merchant_key_store, + auth_token, + payment_intent, + ) + .await } } } diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 044e270a7ea9..84aef952a531 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -13,6 +13,7 @@ use api_models::{ ResponsePaymentMethodsEnabled, }, payments::BankCodeResponse, + pm_auth::PaymentMethodAuthConfig, surcharge_decision_configs as api_surcharge_decision_configs, }; use common_utils::{ @@ -25,7 +26,12 @@ use error_stack::{report, IntoReport, ResultExt}; use masking::Secret; use router_env::{instrument, tracing}; -use super::surcharge_decision_configs::perform_surcharge_decision_management_for_payment_method_list; +use super::surcharge_decision_configs::{ + perform_surcharge_decision_management_for_payment_method_list, + perform_surcharge_decision_management_for_saved_cards, +}; +#[cfg(not(feature = "connector_choice_mca_id"))] +use crate::core::utils::get_connector_label; use crate::{ configs::settings, core::{ @@ -38,7 +44,6 @@ use crate::{ helpers, routing::{self, SessionFlowRoutingInput}, }, - utils::persist_individual_surcharge_details_in_redis, }, db, logger, pii::prelude::*, @@ -225,12 +230,21 @@ pub async fn add_card_to_locker( ) .await .map_err(|error| { - metrics::CARD_LOCKER_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::CARD_LOCKER_FAILURES.add( + &metrics::CONTEXT, + 1, + &[ + router_env::opentelemetry::KeyValue::new("locker", "basilisk"), + router_env::opentelemetry::KeyValue::new("operation", "add"), + ], + ); error }) }, &metrics::CARD_ADD_TIME, - &[], + &[router_env::opentelemetry::KeyValue::new( + "locker", "basilisk", + )], ) .await?; logger::debug!("card added to basilisk locker"); @@ -248,22 +262,45 @@ pub async fn add_card_to_locker( ) .await .map_err(|error| { - metrics::CARD_LOCKER_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::CARD_LOCKER_FAILURES.add( + &metrics::CONTEXT, + 1, + &[ + router_env::opentelemetry::KeyValue::new("locker", "rust"), + router_env::opentelemetry::KeyValue::new("operation", "add"), + ], + ); error }) }, &metrics::CARD_ADD_TIME, - &[], + &[router_env::opentelemetry::KeyValue::new("locker", "rust")], ) .await; match add_card_to_rs_resp { value @ Ok(_) => { - logger::debug!("Card added successfully"); + logger::debug!("card added to rust locker"); + let _ = &metrics::CARD_LOCKER_SUCCESSFUL_RESPONSE.add( + &metrics::CONTEXT, + 1, + &[ + router_env::opentelemetry::KeyValue::new("locker", "rust"), + router_env::opentelemetry::KeyValue::new("operation", "add"), + ], + ); value } Err(err) => { - logger::debug!(error =? err,"failed to add card"); + logger::debug!(error =? err,"failed to add card to rust locker"); + let _ = &metrics::CARD_LOCKER_SUCCESSFUL_RESPONSE.add( + &metrics::CONTEXT, + 1, + &[ + router_env::opentelemetry::KeyValue::new("locker", "basilisk"), + router_env::opentelemetry::KeyValue::new("operation", "add"), + ], + ); Ok(add_card_to_hs_resp) } } @@ -290,12 +327,19 @@ pub async fn get_card_from_locker( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed while getting card from basilisk_hs") .map_err(|error| { - metrics::CARD_LOCKER_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::CARD_LOCKER_FAILURES.add( + &metrics::CONTEXT, + 1, + &[ + router_env::opentelemetry::KeyValue::new("locker", "rust"), + router_env::opentelemetry::KeyValue::new("operation", "get"), + ], + ); error }) }, &metrics::CARD_GET_TIME, - &[], + &[router_env::opentelemetry::KeyValue::new("locker", "rust")], ) .await; @@ -313,20 +357,45 @@ pub async fn get_card_from_locker( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed while getting card from basilisk_hs") .map_err(|error| { - metrics::CARD_LOCKER_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::CARD_LOCKER_FAILURES.add( + &metrics::CONTEXT, + 1, + &[ + router_env::opentelemetry::KeyValue::new("locker", "basilisk"), + router_env::opentelemetry::KeyValue::new("operation", "get"), + ], + ); error }) }, &metrics::CARD_GET_TIME, - &[], + &[router_env::opentelemetry::KeyValue::new( + "locker", "basilisk", + )], ) .await .map(|inner_card| { logger::debug!("card retrieved from basilisk locker"); + let _ = &metrics::CARD_LOCKER_SUCCESSFUL_RESPONSE.add( + &metrics::CONTEXT, + 1, + &[ + router_env::opentelemetry::KeyValue::new("locker", "basilisk"), + router_env::opentelemetry::KeyValue::new("operation", "get"), + ], + ); inner_card }), Ok(_) => { logger::debug!("card retrieved from rust locker"); + let _ = &metrics::CARD_LOCKER_SUCCESSFUL_RESPONSE.add( + &metrics::CONTEXT, + 1, + &[ + router_env::opentelemetry::KeyValue::new("locker", "rust"), + router_env::opentelemetry::KeyValue::new("operation", "get"), + ], + ); get_card_from_rs_locker_resp } } @@ -1015,9 +1084,9 @@ pub async fn list_payment_methods( logger::debug!(mca_before_filtering=?filtered_mcas); let mut response: Vec = vec![]; - for mca in filtered_mcas { - let payment_methods = match mca.payment_methods_enabled { - Some(pm) => pm, + for mca in &filtered_mcas { + let payment_methods = match &mca.payment_methods_enabled { + Some(pm) => pm.clone(), None => continue, }; @@ -1028,13 +1097,15 @@ pub async fn list_payment_methods( payment_intent.as_ref(), payment_attempt.as_ref(), billing_address.as_ref(), - mca.connector_name, + mca.connector_name.clone(), pm_config_mapping, &state.conf.mandates.supported_payment_methods, ) .await?; } + let mut pmt_to_auth_connector = HashMap::new(); + if let Some((payment_attempt, payment_intent)) = payment_attempt.as_ref().zip(payment_intent.as_ref()) { @@ -1138,6 +1209,84 @@ pub async fn list_payment_methods( pre_routing_results.insert(pm_type, routable_choice); } + let redis_conn = db + .get_redis_conn() + .map_err(|redis_error| logger::error!(?redis_error)) + .ok(); + + let mut val = Vec::new(); + + for (payment_method_type, routable_connector_choice) in &pre_routing_results { + #[cfg(not(feature = "connector_choice_mca_id"))] + let connector_label = get_connector_label( + payment_intent.business_country, + payment_intent.business_label.as_ref(), + #[cfg(not(feature = "connector_choice_mca_id"))] + routable_connector_choice.sub_label.as_ref(), + #[cfg(feature = "connector_choice_mca_id")] + None, + routable_connector_choice.connector.to_string().as_str(), + ); + #[cfg(not(feature = "connector_choice_mca_id"))] + let matched_mca = filtered_mcas + .iter() + .find(|m| connector_label == m.connector_label); + + #[cfg(feature = "connector_choice_mca_id")] + let matched_mca = filtered_mcas.iter().find(|m| { + routable_connector_choice.merchant_connector_id.as_ref() + == Some(&m.merchant_connector_id) + }); + + if let Some(m) = matched_mca { + let pm_auth_config = m + .pm_auth_config + .as_ref() + .map(|config| { + serde_json::from_value::(config.clone()) + .into_report() + .change_context(errors::StorageError::DeserializationFailed) + .attach_printable("Failed to deserialize Payment Method Auth config") + }) + .transpose() + .unwrap_or_else(|err| { + logger::error!(error=?err); + None + }); + + let matched_config = match pm_auth_config { + Some(config) => { + let internal_config = config + .enabled_payment_methods + .iter() + .find(|config| config.payment_method_type == *payment_method_type) + .cloned(); + + internal_config + } + None => None, + }; + + if let Some(config) = matched_config { + pmt_to_auth_connector + .insert(*payment_method_type, config.connector_name.clone()); + val.push(config); + } + } + } + + let pm_auth_key = format!("pm_auth_{}", payment_intent.payment_id); + let redis_expiry = state.conf.payment_method_auth.redis_expiry; + + if let Some(rc) = redis_conn { + rc.serialize_and_set_key_with_expiry(pm_auth_key.as_str(), val, redis_expiry) + .await + .attach_printable("Failed to store pm auth data in redis") + .unwrap_or_else(|err| { + logger::error!(error=?err); + }) + }; + routing_info.pre_routing_results = Some(pre_routing_results); let encoded = utils::Encode::::encode_to_value(&routing_info) @@ -1395,7 +1544,9 @@ pub async fn list_payment_methods( .and_then(|inner_hm| inner_hm.get(payment_method_types_hm.0)) .cloned(), surcharge_details: None, - pm_auth_connector: None, + pm_auth_connector: pmt_to_auth_connector + .get(payment_method_types_hm.0) + .cloned(), }) } @@ -1430,7 +1581,9 @@ pub async fn list_payment_methods( .and_then(|inner_hm| inner_hm.get(payment_method_types_hm.0)) .cloned(), surcharge_details: None, - pm_auth_connector: None, + pm_auth_connector: pmt_to_auth_connector + .get(payment_method_types_hm.0) + .cloned(), }) } @@ -1460,7 +1613,7 @@ pub async fn list_payment_methods( .and_then(|inner_hm| inner_hm.get(key.0)) .cloned(), surcharge_details: None, - pm_auth_connector: None, + pm_auth_connector: pmt_to_auth_connector.get(&payment_method_type).cloned(), } }) } @@ -1493,7 +1646,7 @@ pub async fn list_payment_methods( .and_then(|inner_hm| inner_hm.get(key.0)) .cloned(), surcharge_details: None, - pm_auth_connector: None, + pm_auth_connector: pmt_to_auth_connector.get(&payment_method_type).cloned(), } }) } @@ -1526,7 +1679,7 @@ pub async fn list_payment_methods( .and_then(|inner_hm| inner_hm.get(key.0)) .cloned(), surcharge_details: None, - pm_auth_connector: None, + pm_auth_connector: pmt_to_auth_connector.get(&payment_method_type).cloned(), } }) } @@ -1623,12 +1776,9 @@ pub async fn call_surcharge_decision_management( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("error performing surcharge decision operation")?; if !surcharge_results.is_empty_result() { - persist_individual_surcharge_details_in_redis( - &state, - merchant_account, - &surcharge_results, - ) - .await?; + surcharge_results + .persist_individual_surcharge_details_in_redis(&state, merchant_account) + .await?; let _ = state .store .update_payment_intent( @@ -1647,6 +1797,56 @@ pub async fn call_surcharge_decision_management( } } +pub async fn call_surcharge_decision_management_for_saved_card( + state: &routes::AppState, + merchant_account: &domain::MerchantAccount, + payment_attempt: &storage::PaymentAttempt, + payment_intent: storage::PaymentIntent, + customer_payment_method_response: &mut api::CustomerPaymentMethodsListResponse, +) -> errors::RouterResult<()> { + if payment_attempt.surcharge_amount.is_some() { + Ok(()) + } else { + let algorithm_ref: routing_types::RoutingAlgorithmRef = merchant_account + .routing_algorithm + .clone() + .map(|val| val.parse_value("routing algorithm")) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Could not decode the routing algorithm")? + .unwrap_or_default(); + let surcharge_results = perform_surcharge_decision_management_for_saved_cards( + state, + algorithm_ref, + payment_attempt, + &payment_intent, + &mut customer_payment_method_response.customer_payment_methods, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("error performing surcharge decision operation")?; + if !surcharge_results.is_empty_result() { + surcharge_results + .persist_individual_surcharge_details_in_redis(state, merchant_account) + .await?; + let _ = state + .store + .update_payment_intent( + payment_intent, + storage::PaymentIntentUpdate::SurchargeApplicableUpdate { + surcharge_applicable: true, + updated_by: merchant_account.storage_scheme.to_string(), + }, + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) + .attach_printable("Failed to update surcharge_applicable in Payment Intent"); + } + Ok(()) + } +} + #[allow(clippy::too_many_arguments)] pub async fn filter_payment_methods( payment_methods: Vec, @@ -2131,12 +2331,13 @@ pub async fn do_list_customer_pm_fetch_customer_if_not_passed( .await } else { let cloned_secret = req.and_then(|r| r.client_secret.as_ref().cloned()); - let payment_intent = helpers::verify_payment_intent_time_and_client_secret( - db, - &merchant_account, - cloned_secret, - ) - .await?; + let payment_intent: Option = + helpers::verify_payment_intent_time_and_client_secret( + db, + &merchant_account, + cloned_secret, + ) + .await?; let customer_id = payment_intent .as_ref() .and_then(|intent| intent.customer_id.to_owned()) @@ -2262,6 +2463,7 @@ pub async fn list_customer_payment_method( created: Some(pm.created_at), bank_transfer: pmd, bank: bank_details, + surcharge_details: None, requires_cvv, }; customer_pms.push(pma.to_owned()); @@ -2313,9 +2515,36 @@ pub async fn list_customer_payment_method( } } - let response = api::CustomerPaymentMethodsListResponse { + let mut response = api::CustomerPaymentMethodsListResponse { customer_payment_methods: customer_pms, }; + let payment_attempt = payment_intent + .as_ref() + .async_map(|payment_intent| async { + state + .store + .find_payment_attempt_by_payment_id_merchant_id_attempt_id( + &payment_intent.payment_id, + &merchant_account.merchant_id, + &payment_intent.active_attempt.get_id(), + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) + }) + .await + .transpose()?; + + if let Some((payment_attempt, payment_intent)) = payment_attempt.zip(payment_intent) { + call_surcharge_decision_management_for_saved_card( + state, + &merchant_account, + &payment_attempt, + payment_intent, + &mut response, + ) + .await?; + } Ok(services::ApplicationResponse::Json(response)) } diff --git a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs index 9a65ec76f2a5..e130795e945a 100644 --- a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs +++ b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs @@ -1,12 +1,10 @@ use api_models::{ - payment_methods::{self, SurchargeDetailsResponse, SurchargeMetadata}, + payment_methods::SurchargeDetailsResponse, payments::Address, routing, - surcharge_decision_configs::{ - self, SurchargeDecisionConfigs, SurchargeDecisionManagerRecord, SurchargeDetails, - }, + surcharge_decision_configs::{self, SurchargeDecisionConfigs, SurchargeDecisionManagerRecord}, }; -use common_utils::{ext_traits::StringExt, static_cache::StaticCache}; +use common_utils::{ext_traits::StringExt, static_cache::StaticCache, types as common_utils_types}; use error_stack::{self, IntoReport, ResultExt}; use euclid::{ backend, @@ -14,7 +12,11 @@ use euclid::{ }; use router_env::{instrument, tracing}; -use crate::{core::payments::PaymentData, db::StorageInterface, types::storage as oss_storage}; +use crate::{ + core::payments::{types, PaymentData}, + db::StorageInterface, + types::{storage as oss_storage, transformers::ForeignTryFrom}, +}; static CONF_CACHE: StaticCache = StaticCache::new(); use crate::{ core::{ @@ -55,10 +57,10 @@ pub async fn perform_surcharge_decision_management_for_payment_method_list( billing_address: Option
, response_payment_method_types: &mut [api_models::payment_methods::ResponsePaymentMethodsEnabled], ) -> ConditionalConfigResult<( - SurchargeMetadata, + types::SurchargeMetadata, surcharge_decision_configs::MerchantSurchargeConfigs, )> { - let mut surcharge_metadata = SurchargeMetadata::new(payment_attempt.attempt_id.clone()); + let mut surcharge_metadata = types::SurchargeMetadata::new(payment_attempt.attempt_id.clone()); let algorithm_id = if let Some(id) = algorithm_ref.surcharge_config_algo_id { id } else { @@ -101,20 +103,29 @@ pub async fn perform_surcharge_decision_management_for_payment_method_list( Some(card_network_type.card_network.clone()); let surcharge_output = execute_dsl_and_get_conditional_config(backend_input.clone(), interpreter)?; + // let surcharge_details = card_network_type.surcharge_details = surcharge_output .surcharge_details .map(|surcharge_details| { - get_surcharge_details_response(surcharge_details, payment_attempt).map( - |surcharge_details_response| { - surcharge_metadata.insert_surcharge_details( - &payment_methods_enabled.payment_method, - &payment_method_type_response.payment_method_type, - Some(&card_network_type.card_network), - surcharge_details_response.clone(), - ); - surcharge_details_response - }, - ) + let surcharge_details = get_surcharge_details_from_surcharge_output( + surcharge_details, + payment_attempt, + )?; + surcharge_metadata.insert_surcharge_details( + types::SurchargeKey::PaymentMethodData( + payment_methods_enabled.payment_method, + payment_method_type_response.payment_method_type, + Some(card_network_type.card_network.clone()), + ), + surcharge_details.clone(), + ); + SurchargeDetailsResponse::foreign_try_from(( + &surcharge_details, + payment_attempt, + )) + .into_report() + .change_context(ConfigError::DslExecutionError) + .attach_printable("Error while constructing Surcharge response type") }) .transpose()?; } @@ -124,17 +135,25 @@ pub async fn perform_surcharge_decision_management_for_payment_method_list( payment_method_type_response.surcharge_details = surcharge_output .surcharge_details .map(|surcharge_details| { - get_surcharge_details_response(surcharge_details, payment_attempt).map( - |surcharge_details_response| { - surcharge_metadata.insert_surcharge_details( - &payment_methods_enabled.payment_method, - &payment_method_type_response.payment_method_type, - None, - surcharge_details_response.clone(), - ); - surcharge_details_response - }, - ) + let surcharge_details = get_surcharge_details_from_surcharge_output( + surcharge_details, + payment_attempt, + )?; + surcharge_metadata.insert_surcharge_details( + types::SurchargeKey::PaymentMethodData( + payment_methods_enabled.payment_method, + payment_method_type_response.payment_method_type, + None, + ), + surcharge_details.clone(), + ); + SurchargeDetailsResponse::foreign_try_from(( + &surcharge_details, + payment_attempt, + )) + .into_report() + .change_context(ConfigError::DslExecutionError) + .attach_printable("Error while constructing Surcharge response type") }) .transpose()?; } @@ -148,12 +167,12 @@ pub async fn perform_surcharge_decision_management_for_session_flow( algorithm_ref: routing::RoutingAlgorithmRef, payment_data: &mut PaymentData, payment_method_type_list: &Vec, -) -> ConditionalConfigResult +) -> ConditionalConfigResult where O: Send + Clone, { let mut surcharge_metadata = - SurchargeMetadata::new(payment_data.payment_attempt.attempt_id.clone()); + types::SurchargeMetadata::new(payment_data.payment_attempt.attempt_id.clone()); let algorithm_id = if let Some(id) = algorithm_ref.surcharge_config_algo_id { id } else { @@ -186,26 +205,95 @@ where let surcharge_output = execute_dsl_and_get_conditional_config(backend_input.clone(), interpreter)?; if let Some(surcharge_details) = surcharge_output.surcharge_details { - let surcharge_details_response = - get_surcharge_details_response(surcharge_details, &payment_data.payment_attempt)?; + let surcharge_details = get_surcharge_details_from_surcharge_output( + surcharge_details, + &payment_data.payment_attempt, + )?; + surcharge_metadata.insert_surcharge_details( + types::SurchargeKey::PaymentMethodData( + payment_method_type.to_owned().into(), + *payment_method_type, + None, + ), + surcharge_details, + ); + } + } + Ok(surcharge_metadata) +} +pub async fn perform_surcharge_decision_management_for_saved_cards( + state: &AppState, + algorithm_ref: routing::RoutingAlgorithmRef, + payment_attempt: &oss_storage::PaymentAttempt, + payment_intent: &oss_storage::PaymentIntent, + customer_payment_method_list: &mut [api_models::payment_methods::CustomerPaymentMethod], +) -> ConditionalConfigResult { + let mut surcharge_metadata = types::SurchargeMetadata::new(payment_attempt.attempt_id.clone()); + let algorithm_id = if let Some(id) = algorithm_ref.surcharge_config_algo_id { + id + } else { + return Ok(surcharge_metadata); + }; + + let key = ensure_algorithm_cached( + &*state.store, + &payment_attempt.merchant_id, + algorithm_ref.timestamp, + algorithm_id.as_str(), + ) + .await?; + let cached_algo = CONF_CACHE + .retrieve(&key) + .into_report() + .change_context(ConfigError::CacheMiss) + .attach_printable("Unable to retrieve cached routing algorithm even after refresh")?; + let mut backend_input = make_dsl_input_for_surcharge(payment_attempt, payment_intent, None) + .change_context(ConfigError::InputConstructionError)?; + let interpreter = &cached_algo.cached_alogorith; + + for customer_payment_method in customer_payment_method_list.iter_mut() { + backend_input.payment_method.payment_method = Some(customer_payment_method.payment_method); + backend_input.payment_method.payment_method_type = + customer_payment_method.payment_method_type; + backend_input.payment_method.card_network = customer_payment_method + .card + .as_ref() + .and_then(|card| card.scheme.as_ref()) + .map(|scheme| { + scheme + .clone() + .parse_enum("CardNetwork") + .change_context(ConfigError::DslExecutionError) + }) + .transpose()?; + let surcharge_output = + execute_dsl_and_get_conditional_config(backend_input.clone(), interpreter)?; + if let Some(surcharge_details_output) = surcharge_output.surcharge_details { + let surcharge_details = get_surcharge_details_from_surcharge_output( + surcharge_details_output, + payment_attempt, + )?; surcharge_metadata.insert_surcharge_details( - &payment_method_type.to_owned().into(), - payment_method_type, - None, - surcharge_details_response, + types::SurchargeKey::Token(customer_payment_method.payment_token.clone()), + surcharge_details.clone(), + ); + customer_payment_method.surcharge_details = Some( + SurchargeDetailsResponse::foreign_try_from((&surcharge_details, payment_attempt)) + .into_report() + .change_context(ConfigError::DslParsingError)?, ); } } Ok(surcharge_metadata) } -fn get_surcharge_details_response( - surcharge_details: SurchargeDetails, +fn get_surcharge_details_from_surcharge_output( + surcharge_details: surcharge_decision_configs::SurchargeDetailsOutput, payment_attempt: &oss_storage::PaymentAttempt, -) -> ConditionalConfigResult { +) -> ConditionalConfigResult { let surcharge_amount = match surcharge_details.surcharge.clone() { - surcharge_decision_configs::Surcharge::Fixed(value) => value, - surcharge_decision_configs::Surcharge::Rate(percentage) => percentage + surcharge_decision_configs::SurchargeOutput::Fixed { amount } => amount, + surcharge_decision_configs::SurchargeOutput::Rate(percentage) => percentage .apply_and_ceil_result(payment_attempt.amount) .change_context(ConfigError::DslExecutionError) .attach_printable("Failed to Calculate surcharge amount by applying percentage")?, @@ -221,13 +309,13 @@ fn get_surcharge_details_response( }) .transpose()? .unwrap_or(0); - Ok(SurchargeDetailsResponse { + Ok(types::SurchargeDetails { surcharge: match surcharge_details.surcharge { - surcharge_decision_configs::Surcharge::Fixed(surcharge_amount) => { - payment_methods::Surcharge::Fixed(surcharge_amount) + surcharge_decision_configs::SurchargeOutput::Fixed { amount } => { + common_utils_types::Surcharge::Fixed(amount) } - surcharge_decision_configs::Surcharge::Rate(percentage) => { - payment_methods::Surcharge::Rate(percentage) + surcharge_decision_configs::SurchargeOutput::Rate(percentage) => { + common_utils_types::Surcharge::Rate(percentage) } }, tax_on_surcharge: surcharge_details.tax_on_surcharge, diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 33afa29397e1..73af17f9d66b 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -13,12 +13,8 @@ pub mod types; use std::{fmt::Debug, marker::PhantomData, ops::Deref, time::Instant, vec::IntoIter}; -use api_models::{ - self, enums, - payment_methods::{Surcharge, SurchargeDetailsResponse}, - payments::{self, HeaderPayload}, -}; -use common_utils::{ext_traits::AsyncExt, pii}; +use api_models::{self, enums, payments::HeaderPayload}; +use common_utils::{ext_traits::AsyncExt, pii, types::Surcharge}; use data_models::mandates::MandateData; use diesel_models::{ephemeral_key, fraud_check::FraudCheck}; use error_stack::{IntoReport, ResultExt}; @@ -33,8 +29,9 @@ use scheduler::{db::process_tracker::ProcessTrackerExt, errors as sch_errors, ut use time; pub use self::operations::{ - PaymentApprove, PaymentCancel, PaymentCapture, PaymentConfirm, PaymentCreate, PaymentReject, - PaymentResponse, PaymentSession, PaymentStatus, PaymentUpdate, + PaymentApprove, PaymentCancel, PaymentCapture, PaymentConfirm, PaymentCreate, + PaymentIncrementalAuthorization, PaymentReject, PaymentResponse, PaymentSession, PaymentStatus, + PaymentUpdate, }; use self::{ conditional_configs::perform_decision_management, @@ -43,9 +40,9 @@ use self::{ operations::{payment_complete_authorize, BoxedOperation, Operation}, routing::{self as self_routing, SessionFlowRoutingInput}, }; -use super::{ - errors::StorageErrorExt, payment_methods::surcharge_decision_configs, utils as core_utils, -}; +use super::{errors::StorageErrorExt, payment_methods::surcharge_decision_configs}; +#[cfg(feature = "frm")] +use crate::core::fraud_check as frm_core; use crate::{ configs::settings::PaymentMethodTypeTokenFilter, core::{ @@ -175,158 +172,231 @@ where let mut connector_http_status_code = None; let mut external_latency = None; if let Some(connector_details) = connector { - operation - .to_domain()? - .populate_payment_data(state, &mut payment_data, &req, &merchant_account) - .await?; - payment_data = match connector_details { - api::ConnectorCallType::PreDetermined(connector) => { - let schedule_time = if should_add_task_to_process_tracker { - payment_sync::get_sync_process_schedule_time( - &*state.store, - connector.connector.id(), - &merchant_account.merchant_id, - 0, - ) - .await - .into_report() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed while getting process schedule time")? - } else { - None - }; - let router_data = call_connector_service( - state, - &merchant_account, - &key_store, - connector, - &operation, - &mut payment_data, - &customer, - call_connector_action, - &validate_result, - schedule_time, - header_payload, - ) + // Fetch and check FRM configs + #[cfg(feature = "frm")] + let mut frm_info = None; + #[cfg(feature = "frm")] + let db = &*state.store; + #[allow(unused_variables, unused_mut)] + let mut should_continue_transaction: bool = true; + #[cfg(feature = "frm")] + let frm_configs = if state.conf.frm.enabled { + frm_core::call_frm_before_connector_call( + db, + &operation, + &merchant_account, + &mut payment_data, + state, + &mut frm_info, + &customer, + &mut should_continue_transaction, + key_store.clone(), + ) + .await? + } else { + None + }; + #[cfg(feature = "frm")] + logger::debug!( + "should_cancel_transaction: {:?} {:?} ", + frm_configs, + should_continue_transaction + ); + + if should_continue_transaction { + operation + .to_domain()? + .populate_payment_data(state, &mut payment_data, &merchant_account) .await?; - let operation = Box::new(PaymentResponse); - - connector_http_status_code = router_data.connector_http_status_code; - external_latency = router_data.external_latency; - //add connector http status code metrics - add_connector_http_status_code_metrics(connector_http_status_code); - operation - .to_post_update_tracker()? - .update_tracker( + payment_data = match connector_details { + api::ConnectorCallType::PreDetermined(connector) => { + let schedule_time = if should_add_task_to_process_tracker { + payment_sync::get_sync_process_schedule_time( + &*state.store, + connector.connector.id(), + &merchant_account.merchant_id, + 0, + ) + .await + .into_report() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while getting process schedule time")? + } else { + None + }; + let router_data = call_connector_service( state, - &validate_result.payment_id, - payment_data, - router_data, - merchant_account.storage_scheme, + &merchant_account, + &key_store, + connector, + &operation, + &mut payment_data, + &customer, + call_connector_action, + &validate_result, + schedule_time, + header_payload, ) - .await? - } + .await?; + let operation = Box::new(PaymentResponse); + + connector_http_status_code = router_data.connector_http_status_code; + external_latency = router_data.external_latency; + //add connector http status code metrics + add_connector_http_status_code_metrics(connector_http_status_code); + operation + .to_post_update_tracker()? + .update_tracker( + state, + &validate_result.payment_id, + payment_data, + router_data, + merchant_account.storage_scheme, + ) + .await? + } - api::ConnectorCallType::Retryable(connectors) => { - let mut connectors = connectors.into_iter(); + api::ConnectorCallType::Retryable(connectors) => { + let mut connectors = connectors.into_iter(); - let connector_data = get_connector_data(&mut connectors)?; + let connector_data = get_connector_data(&mut connectors)?; - let schedule_time = if should_add_task_to_process_tracker { - payment_sync::get_sync_process_schedule_time( - &*state.store, - connector_data.connector.id(), - &merchant_account.merchant_id, - 0, + let schedule_time = if should_add_task_to_process_tracker { + payment_sync::get_sync_process_schedule_time( + &*state.store, + connector_data.connector.id(), + &merchant_account.merchant_id, + 0, + ) + .await + .into_report() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while getting process schedule time")? + } else { + None + }; + let router_data = call_connector_service( + state, + &merchant_account, + &key_store, + connector_data.clone(), + &operation, + &mut payment_data, + &customer, + call_connector_action, + &validate_result, + schedule_time, + header_payload, ) - .await - .into_report() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed while getting process schedule time")? - } else { - None - }; - let router_data = call_connector_service( - state, - &merchant_account, - &key_store, - connector_data.clone(), - &operation, - &mut payment_data, - &customer, - call_connector_action, - &validate_result, - schedule_time, - header_payload, - ) - .await?; + .await?; - #[cfg(feature = "retry")] - let mut router_data = router_data; - #[cfg(feature = "retry")] - { - use crate::core::payments::retry::{self, GsmValidation}; - let config_bool = - retry::config_should_call_gsm(&*state.store, &merchant_account.merchant_id) - .await; + #[cfg(feature = "retry")] + let mut router_data = router_data; + #[cfg(feature = "retry")] + { + use crate::core::payments::retry::{self, GsmValidation}; + let config_bool = retry::config_should_call_gsm( + &*state.store, + &merchant_account.merchant_id, + ) + .await; + + if config_bool && router_data.should_call_gsm() { + router_data = retry::do_gsm_actions( + state, + &mut payment_data, + connectors, + connector_data, + router_data, + &merchant_account, + &key_store, + &operation, + &customer, + &validate_result, + schedule_time, + ) + .await?; + }; + } - if config_bool && router_data.should_call_gsm() { - router_data = retry::do_gsm_actions( + let operation = Box::new(PaymentResponse); + connector_http_status_code = router_data.connector_http_status_code; + external_latency = router_data.external_latency; + //add connector http status code metrics + add_connector_http_status_code_metrics(connector_http_status_code); + operation + .to_post_update_tracker()? + .update_tracker( state, - &mut payment_data, - connectors, - connector_data, + &validate_result.payment_id, + payment_data, router_data, - &merchant_account, - &key_store, - &operation, - &customer, - &validate_result, - schedule_time, + merchant_account.storage_scheme, ) - .await?; - }; + .await? } - let operation = Box::new(PaymentResponse); - connector_http_status_code = router_data.connector_http_status_code; - external_latency = router_data.external_latency; - //add connector http status code metrics - add_connector_http_status_code_metrics(connector_http_status_code); - operation - .to_post_update_tracker()? - .update_tracker( + api::ConnectorCallType::SessionMultiple(connectors) => { + let session_surcharge_details = + call_surcharge_decision_management_for_session_flow( + state, + &merchant_account, + &mut payment_data, + &connectors, + ) + .await?; + call_multiple_connectors_service( state, - &validate_result.payment_id, + &merchant_account, + &key_store, + connectors, + &operation, payment_data, - router_data, - merchant_account.storage_scheme, + &customer, + session_surcharge_details, ) .await? - } + } + }; - api::ConnectorCallType::SessionMultiple(connectors) => { - let session_surcharge_details = - call_surcharge_decision_management_for_session_flow( - state, - &merchant_account, - &mut payment_data, - &connectors, - ) - .await?; - call_multiple_connectors_service( + #[cfg(feature = "frm")] + if let Some(fraud_info) = &mut frm_info { + Box::pin(frm_core::post_payment_frm_core( state, &merchant_account, - &key_store, - connectors, - &operation, - payment_data, + &mut payment_data, + fraud_info, + frm_configs + .clone() + .ok_or(errors::ApiErrorResponse::MissingRequiredField { + field_name: "frm_configs", + }) + .into_report() + .attach_printable("Frm configs label not found")?, &customer, - session_surcharge_details, - ) - .await? + key_store, + )) + .await?; } - }; + } else { + (_, payment_data) = operation + .to_update_tracker()? + .update_trackers( + state, + payment_data.clone(), + customer.clone(), + validate_result.storage_scheme, + None, + &key_store, + #[cfg(feature = "frm")] + frm_info.and_then(|info| info.suggested_action), + #[cfg(not(feature = "frm"))] + None, + header_payload, + ) + .await?; + } + payment_data .payment_attempt .payment_token @@ -405,7 +475,6 @@ where async fn populate_surcharge_details( state: &AppState, payment_data: &mut PaymentData, - request: &payments::PaymentsRequest, ) -> RouterResult<()> where F: Send + Clone, @@ -415,68 +484,51 @@ where .surcharge_applicable .unwrap_or(false) { - let payment_method_data = request + let raw_card_key = payment_data .payment_method_data - .clone() - .get_required_value("payment_method_data")?; - let (payment_method, payment_method_type, card_network) = - get_key_params_for_surcharge_details(payment_method_data)?; - - let calculated_surcharge_details = match utils::get_individual_surcharge_detail_from_redis( - state, - &payment_method, - &payment_method_type, - card_network, - &payment_data.payment_attempt.attempt_id, - ) - .await - { - Ok(surcharge_details) => Some(surcharge_details), - Err(err) if err.current_context() == &RedisError::NotFound => None, - Err(err) => Err(err).change_context(errors::ApiErrorResponse::InternalServerError)?, - }; + .as_ref() + .map(get_key_params_for_surcharge_details) + .transpose()? + .map(|(payment_method, payment_method_type, card_network)| { + types::SurchargeKey::PaymentMethodData( + payment_method, + payment_method_type, + card_network, + ) + }); + let saved_card_key = payment_data.token.clone().map(types::SurchargeKey::Token); - let request_surcharge_details = request.surcharge_details; + let surcharge_key = raw_card_key + .or(saved_card_key) + .get_required_value("payment_method_data or payment_token")?; + logger::debug!(surcharge_key_confirm =? surcharge_key); - match (request_surcharge_details, calculated_surcharge_details) { - (Some(request_surcharge_details), Some(calculated_surcharge_details)) => { - if calculated_surcharge_details - .is_request_surcharge_matching(request_surcharge_details) - { - payment_data.surcharge_details = Some(calculated_surcharge_details); - } else { - return Err(errors::ApiErrorResponse::InvalidRequestData { - message: "Invalid value provided: 'surcharge_details'. surcharge details provided do not match with surcharge details sent in payment_methods list response".to_string(), - } - .into()); - } - } - (None, Some(_calculated_surcharge_details)) => { - return Err(errors::ApiErrorResponse::MissingRequiredField { - field_name: "surcharge_details", - } - .into()); - } - (Some(request_surcharge_details), None) => { - if request_surcharge_details.is_surcharge_zero() { - return Ok(()); - } else { - return Err(errors::ApiErrorResponse::InvalidRequestData { - message: "Invalid value provided: 'surcharge_details'. surcharge details provided do not match with surcharge details sent in payment_methods list response".to_string(), - } - .into()); + let calculated_surcharge_details = + match types::SurchargeMetadata::get_individual_surcharge_detail_from_redis( + state, + surcharge_key, + &payment_data.payment_attempt.attempt_id, + ) + .await + { + Ok(surcharge_details) => Some(surcharge_details), + Err(err) if err.current_context() == &RedisError::NotFound => None, + Err(err) => { + Err(err).change_context(errors::ApiErrorResponse::InternalServerError)? } - } - (None, None) => return Ok(()), - }; + }; + + payment_data.surcharge_details = calculated_surcharge_details; } else { let surcharge_details = payment_data .payment_attempt .get_surcharge_details() .map(|surcharge_details| { - surcharge_details - .get_surcharge_details_object(payment_data.payment_attempt.amount) + types::SurchargeDetails::from(( + &surcharge_details, + &payment_data.payment_attempt, + )) }); payment_data.surcharge_details = surcharge_details; } @@ -509,7 +561,7 @@ where let final_amount = payment_data.payment_attempt.amount + surcharge_amount + tax_on_surcharge_amount; Ok(Some(api::SessionSurchargeDetails::PreDetermined( - SurchargeDetailsResponse { + types::SurchargeDetails { surcharge: Surcharge::Fixed(surcharge_amount), tax_on_surcharge: None, surcharge_amount, @@ -541,12 +593,9 @@ where .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("error performing surcharge decision operation")?; - core_utils::persist_individual_surcharge_details_in_redis( - state, - merchant_account, - &surcharge_results, - ) - .await?; + surcharge_results + .persist_individual_surcharge_details_in_redis(state, merchant_account) + .await?; Ok(if surcharge_results.is_empty_result() { None @@ -957,6 +1006,11 @@ where merchant_connector_account.get_mca_id(); } + operation + .to_domain()? + .populate_payment_data(state, payment_data, merchant_account) + .await?; + let (pd, tokenization_action) = get_connector_tokenization_action_when_confirm_true( state, operation, @@ -1882,9 +1936,19 @@ where pub recurring_mandate_payment_data: Option, pub ephemeral_key: Option, pub redirect_response: Option, - pub surcharge_details: Option, + pub surcharge_details: Option, pub frm_message: Option, pub payment_link_data: Option, + pub incremental_authorization_details: Option, + pub authorizations: Vec, +} + +#[derive(Debug, Default, Clone)] +pub struct IncrementalAuthorizationDetails { + pub additional_amount: i64, + pub total_amount: i64, + pub reason: Option, + pub authorization_id: Option, } #[derive(Debug, Default, Clone)] @@ -1985,6 +2049,10 @@ pub fn should_call_connector( "CompleteAuthorize" => true, "PaymentApprove" => true, "PaymentSession" => true, + "PaymentIncrementalAuthorization" => matches!( + payment_data.payment_intent.status, + storage_enums::IntentStatus::RequiresCapture + ), _ => false, } } diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 9be6f5905b8b..81ba48e9831f 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -3,6 +3,7 @@ pub mod authorize_flow; pub mod cancel_flow; pub mod capture_flow; pub mod complete_authorize_flow; +pub mod incremental_authorization_flow; pub mod psync_flow; pub mod reject_flow; pub mod session_flow; @@ -10,6 +11,8 @@ pub mod setup_mandate_flow; use async_trait::async_trait; +#[cfg(feature = "frm")] +use crate::types::fraud_check as frm_types; use crate::{ connector, core::{ @@ -169,6 +172,7 @@ default_imp_for_complete_authorize!( connector::Payeezy, connector::Payu, connector::Rapyd, + connector::Signifyd, connector::Square, connector::Stax, connector::Stripe, @@ -246,6 +250,7 @@ default_imp_for_webhook_source_verification!( connector::Prophetpay, connector::Rapyd, connector::Shift4, + connector::Signifyd, connector::Square, connector::Stax, connector::Stripe, @@ -325,6 +330,7 @@ default_imp_for_create_customer!( connector::Prophetpay, connector::Rapyd, connector::Shift4, + connector::Signifyd, connector::Square, connector::Trustpay, connector::Tsys, @@ -393,6 +399,7 @@ default_imp_for_connector_redirect_response!( connector::Prophetpay, connector::Rapyd, connector::Shift4, + connector::Signifyd, connector::Square, connector::Stax, connector::Tsys, @@ -452,6 +459,7 @@ default_imp_for_connector_request_id!( connector::Prophetpay, connector::Rapyd, connector::Shift4, + connector::Signifyd, connector::Square, connector::Stax, connector::Stripe, @@ -534,6 +542,7 @@ default_imp_for_accept_dispute!( connector::Prophetpay, connector::Rapyd, connector::Shift4, + connector::Signifyd, connector::Square, connector::Stax, connector::Stripe, @@ -634,6 +643,7 @@ default_imp_for_file_upload!( connector::Prophetpay, connector::Rapyd, connector::Shift4, + connector::Signifyd, connector::Square, connector::Stax, connector::Trustpay, @@ -712,6 +722,7 @@ default_imp_for_submit_evidence!( connector::Prophetpay, connector::Rapyd, connector::Shift4, + connector::Signifyd, connector::Square, connector::Stax, connector::Trustpay, @@ -790,6 +801,7 @@ default_imp_for_defend_dispute!( connector::Prophetpay, connector::Rapyd, connector::Shift4, + connector::Signifyd, connector::Square, connector::Stax, connector::Stripe, @@ -867,6 +879,7 @@ default_imp_for_pre_processing_steps!( connector::Prophetpay, connector::Rapyd, connector::Shift4, + connector::Signifyd, connector::Square, connector::Stax, connector::Tsys, @@ -927,6 +940,7 @@ default_imp_for_payouts!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Signifyd, connector::Square, connector::Stax, connector::Stripe, @@ -1006,6 +1020,7 @@ default_imp_for_payouts_create!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Signifyd, connector::Square, connector::Stax, connector::Stripe, @@ -1088,6 +1103,7 @@ default_imp_for_payouts_eligibility!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Signifyd, connector::Square, connector::Stax, connector::Stripe, @@ -1167,6 +1183,7 @@ default_imp_for_payouts_fulfill!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Signifyd, connector::Square, connector::Stax, connector::Stripe, @@ -1246,6 +1263,7 @@ default_imp_for_payouts_cancel!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Signifyd, connector::Square, connector::Stax, connector::Stripe, @@ -1326,6 +1344,7 @@ default_imp_for_payouts_quote!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Signifyd, connector::Square, connector::Stax, connector::Stripe, @@ -1406,6 +1425,7 @@ default_imp_for_payouts_recipient!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Signifyd, connector::Square, connector::Stax, connector::Stripe, @@ -1485,6 +1505,7 @@ default_imp_for_approve!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Signifyd, connector::Square, connector::Stax, connector::Stripe, @@ -1565,6 +1586,561 @@ default_imp_for_reject!( connector::Powertranz, connector::Prophetpay, connector::Rapyd, + connector::Signifyd, + connector::Square, + connector::Stax, + connector::Stripe, + connector::Shift4, + connector::Trustpay, + connector::Tsys, + connector::Volt, + connector::Wise, + connector::Worldline, + connector::Worldpay, + connector::Zen +); + +macro_rules! default_imp_for_fraud_check { + ($($path:ident::$connector:ident),*) => { + $( + impl api::FraudCheck for $path::$connector {} + )* + }; +} + +#[cfg(feature = "dummy_connector")] +impl api::FraudCheck for connector::DummyConnector {} + +default_imp_for_fraud_check!( + connector::Aci, + connector::Adyen, + connector::Airwallex, + connector::Authorizedotnet, + connector::Bambora, + connector::Bankofamerica, + connector::Bitpay, + connector::Bluesnap, + connector::Boku, + connector::Braintree, + connector::Cashtocode, + connector::Checkout, + connector::Cryptopay, + connector::Cybersource, + connector::Coinbase, + connector::Dlocal, + connector::Fiserv, + connector::Forte, + connector::Globalpay, + connector::Globepay, + connector::Gocardless, + connector::Helcim, + connector::Iatapay, + connector::Klarna, + connector::Mollie, + connector::Multisafepay, + connector::Nexinets, + connector::Nmi, + connector::Noon, + connector::Nuvei, + connector::Opayo, + connector::Opennode, + connector::Payeezy, + connector::Payme, + connector::Paypal, + connector::Payu, + connector::Powertranz, + connector::Prophetpay, + connector::Rapyd, + connector::Square, + connector::Stax, + connector::Stripe, + connector::Shift4, + connector::Trustpay, + connector::Tsys, + connector::Volt, + connector::Wise, + connector::Worldline, + connector::Worldpay, + connector::Zen +); + +#[cfg(feature = "frm")] +macro_rules! default_imp_for_frm_sale { + ($($path:ident::$connector:ident),*) => { + $( + impl api::FraudCheckSale for $path::$connector {} + impl + services::ConnectorIntegration< + api::Sale, + frm_types::FraudCheckSaleData, + frm_types::FraudCheckResponseData, + > for $path::$connector + {} + )* + }; +} + +#[cfg(all(feature = "frm", feature = "dummy_connector"))] +impl api::FraudCheckSale for connector::DummyConnector {} +#[cfg(all(feature = "frm", feature = "dummy_connector"))] +impl + services::ConnectorIntegration< + api::Sale, + frm_types::FraudCheckSaleData, + frm_types::FraudCheckResponseData, + > for connector::DummyConnector +{ +} + +#[cfg(feature = "frm")] +default_imp_for_frm_sale!( + connector::Aci, + connector::Adyen, + connector::Airwallex, + connector::Authorizedotnet, + connector::Bambora, + connector::Bankofamerica, + connector::Bitpay, + connector::Bluesnap, + connector::Boku, + connector::Braintree, + connector::Cashtocode, + connector::Checkout, + connector::Cryptopay, + connector::Cybersource, + connector::Coinbase, + connector::Dlocal, + connector::Fiserv, + connector::Forte, + connector::Globalpay, + connector::Globepay, + connector::Gocardless, + connector::Helcim, + connector::Iatapay, + connector::Klarna, + connector::Mollie, + connector::Multisafepay, + connector::Nexinets, + connector::Nmi, + connector::Noon, + connector::Nuvei, + connector::Opayo, + connector::Opennode, + connector::Payeezy, + connector::Payme, + connector::Paypal, + connector::Payu, + connector::Powertranz, + connector::Prophetpay, + connector::Rapyd, + connector::Square, + connector::Stax, + connector::Stripe, + connector::Shift4, + connector::Trustpay, + connector::Tsys, + connector::Volt, + connector::Wise, + connector::Worldline, + connector::Worldpay, + connector::Zen +); + +#[cfg(feature = "frm")] +macro_rules! default_imp_for_frm_checkout { + ($($path:ident::$connector:ident),*) => { + $( + impl api::FraudCheckCheckout for $path::$connector {} + impl + services::ConnectorIntegration< + api::Checkout, + frm_types::FraudCheckCheckoutData, + frm_types::FraudCheckResponseData, + > for $path::$connector + {} + )* + }; +} + +#[cfg(all(feature = "frm", feature = "dummy_connector"))] +impl api::FraudCheckCheckout for connector::DummyConnector {} +#[cfg(all(feature = "frm", feature = "dummy_connector"))] +impl + services::ConnectorIntegration< + api::Checkout, + frm_types::FraudCheckCheckoutData, + frm_types::FraudCheckResponseData, + > for connector::DummyConnector +{ +} + +#[cfg(feature = "frm")] +default_imp_for_frm_checkout!( + connector::Aci, + connector::Adyen, + connector::Airwallex, + connector::Authorizedotnet, + connector::Bambora, + connector::Bankofamerica, + connector::Bitpay, + connector::Bluesnap, + connector::Boku, + connector::Braintree, + connector::Cashtocode, + connector::Checkout, + connector::Cryptopay, + connector::Cybersource, + connector::Coinbase, + connector::Dlocal, + connector::Fiserv, + connector::Forte, + connector::Globalpay, + connector::Globepay, + connector::Gocardless, + connector::Helcim, + connector::Iatapay, + connector::Klarna, + connector::Mollie, + connector::Multisafepay, + connector::Nexinets, + connector::Nmi, + connector::Noon, + connector::Nuvei, + connector::Opayo, + connector::Opennode, + connector::Payeezy, + connector::Payme, + connector::Paypal, + connector::Payu, + connector::Powertranz, + connector::Prophetpay, + connector::Rapyd, + connector::Square, + connector::Stax, + connector::Stripe, + connector::Shift4, + connector::Trustpay, + connector::Tsys, + connector::Volt, + connector::Wise, + connector::Worldline, + connector::Worldpay, + connector::Zen +); + +#[cfg(feature = "frm")] +macro_rules! default_imp_for_frm_transaction { + ($($path:ident::$connector:ident),*) => { + $( + impl api::FraudCheckTransaction for $path::$connector {} + impl + services::ConnectorIntegration< + api::Transaction, + frm_types::FraudCheckTransactionData, + frm_types::FraudCheckResponseData, + > for $path::$connector + {} + )* + }; +} + +#[cfg(all(feature = "frm", feature = "dummy_connector"))] +impl api::FraudCheckTransaction for connector::DummyConnector {} +#[cfg(all(feature = "frm", feature = "dummy_connector"))] +impl + services::ConnectorIntegration< + api::Transaction, + frm_types::FraudCheckTransactionData, + frm_types::FraudCheckResponseData, + > for connector::DummyConnector +{ +} + +#[cfg(feature = "frm")] +default_imp_for_frm_transaction!( + connector::Aci, + connector::Adyen, + connector::Airwallex, + connector::Authorizedotnet, + connector::Bambora, + connector::Bankofamerica, + connector::Bitpay, + connector::Bluesnap, + connector::Boku, + connector::Braintree, + connector::Cashtocode, + connector::Checkout, + connector::Cryptopay, + connector::Cybersource, + connector::Coinbase, + connector::Dlocal, + connector::Fiserv, + connector::Forte, + connector::Globalpay, + connector::Globepay, + connector::Gocardless, + connector::Helcim, + connector::Iatapay, + connector::Klarna, + connector::Mollie, + connector::Multisafepay, + connector::Nexinets, + connector::Nmi, + connector::Noon, + connector::Nuvei, + connector::Opayo, + connector::Opennode, + connector::Payeezy, + connector::Payme, + connector::Paypal, + connector::Payu, + connector::Powertranz, + connector::Prophetpay, + connector::Rapyd, + connector::Square, + connector::Stax, + connector::Stripe, + connector::Shift4, + connector::Trustpay, + connector::Tsys, + connector::Volt, + connector::Wise, + connector::Worldline, + connector::Worldpay, + connector::Zen +); + +#[cfg(feature = "frm")] +macro_rules! default_imp_for_frm_fulfillment { + ($($path:ident::$connector:ident),*) => { + $( + impl api::FraudCheckFulfillment for $path::$connector {} + impl + services::ConnectorIntegration< + api::Fulfillment, + frm_types::FraudCheckFulfillmentData, + frm_types::FraudCheckResponseData, + > for $path::$connector + {} + )* + }; +} + +#[cfg(all(feature = "frm", feature = "dummy_connector"))] +impl api::FraudCheckFulfillment for connector::DummyConnector {} +#[cfg(all(feature = "frm", feature = "dummy_connector"))] +impl + services::ConnectorIntegration< + api::Fulfillment, + frm_types::FraudCheckFulfillmentData, + frm_types::FraudCheckResponseData, + > for connector::DummyConnector +{ +} + +#[cfg(feature = "frm")] +default_imp_for_frm_fulfillment!( + connector::Aci, + connector::Adyen, + connector::Airwallex, + connector::Authorizedotnet, + connector::Bambora, + connector::Bankofamerica, + connector::Bitpay, + connector::Bluesnap, + connector::Boku, + connector::Braintree, + connector::Cashtocode, + connector::Checkout, + connector::Cryptopay, + connector::Cybersource, + connector::Coinbase, + connector::Dlocal, + connector::Fiserv, + connector::Forte, + connector::Globalpay, + connector::Globepay, + connector::Gocardless, + connector::Helcim, + connector::Iatapay, + connector::Klarna, + connector::Mollie, + connector::Multisafepay, + connector::Nexinets, + connector::Nmi, + connector::Noon, + connector::Nuvei, + connector::Opayo, + connector::Opennode, + connector::Payeezy, + connector::Payme, + connector::Paypal, + connector::Payu, + connector::Powertranz, + connector::Prophetpay, + connector::Rapyd, + connector::Square, + connector::Stax, + connector::Stripe, + connector::Shift4, + connector::Trustpay, + connector::Tsys, + connector::Volt, + connector::Wise, + connector::Worldline, + connector::Worldpay, + connector::Zen +); + +#[cfg(feature = "frm")] +macro_rules! default_imp_for_frm_record_return { + ($($path:ident::$connector:ident),*) => { + $( + impl api::FraudCheckRecordReturn for $path::$connector {} + impl + services::ConnectorIntegration< + api::RecordReturn, + frm_types::FraudCheckRecordReturnData, + frm_types::FraudCheckResponseData, + > for $path::$connector + {} + )* + }; +} + +#[cfg(all(feature = "frm", feature = "dummy_connector"))] +impl api::FraudCheckRecordReturn for connector::DummyConnector {} +#[cfg(all(feature = "frm", feature = "dummy_connector"))] +impl + services::ConnectorIntegration< + api::RecordReturn, + frm_types::FraudCheckRecordReturnData, + frm_types::FraudCheckResponseData, + > for connector::DummyConnector +{ +} + +#[cfg(feature = "frm")] +default_imp_for_frm_record_return!( + connector::Aci, + connector::Adyen, + connector::Airwallex, + connector::Authorizedotnet, + connector::Bambora, + connector::Bankofamerica, + connector::Bitpay, + connector::Bluesnap, + connector::Boku, + connector::Braintree, + connector::Cashtocode, + connector::Checkout, + connector::Cryptopay, + connector::Cybersource, + connector::Coinbase, + connector::Dlocal, + connector::Fiserv, + connector::Forte, + connector::Globalpay, + connector::Globepay, + connector::Gocardless, + connector::Helcim, + connector::Iatapay, + connector::Klarna, + connector::Mollie, + connector::Multisafepay, + connector::Nexinets, + connector::Nmi, + connector::Noon, + connector::Nuvei, + connector::Opayo, + connector::Opennode, + connector::Payeezy, + connector::Payme, + connector::Paypal, + connector::Payu, + connector::Powertranz, + connector::Prophetpay, + connector::Rapyd, + connector::Square, + connector::Stax, + connector::Stripe, + connector::Shift4, + connector::Trustpay, + connector::Tsys, + connector::Volt, + connector::Wise, + connector::Worldline, + connector::Worldpay, + connector::Zen +); + +macro_rules! default_imp_for_incremental_authorization { + ($($path:ident::$connector:ident),*) => { + $( + impl api::PaymentIncrementalAuthorization for $path::$connector {} + impl + services::ConnectorIntegration< + api::IncrementalAuthorization, + types::PaymentsIncrementalAuthorizationData, + types::PaymentsResponseData, + > for $path::$connector + {} + )* + }; +} + +#[cfg(feature = "dummy_connector")] +impl api::PaymentIncrementalAuthorization for connector::DummyConnector {} +#[cfg(feature = "dummy_connector")] +impl + services::ConnectorIntegration< + api::IncrementalAuthorization, + types::PaymentsIncrementalAuthorizationData, + types::PaymentsResponseData, + > for connector::DummyConnector +{ +} + +default_imp_for_incremental_authorization!( + connector::Aci, + connector::Adyen, + connector::Airwallex, + connector::Authorizedotnet, + connector::Bambora, + connector::Bankofamerica, + connector::Bitpay, + connector::Bluesnap, + connector::Boku, + connector::Braintree, + connector::Cashtocode, + connector::Checkout, + connector::Cryptopay, + connector::Coinbase, + connector::Dlocal, + connector::Fiserv, + connector::Forte, + connector::Globalpay, + connector::Globepay, + connector::Gocardless, + connector::Helcim, + connector::Iatapay, + connector::Klarna, + connector::Mollie, + connector::Multisafepay, + connector::Nexinets, + connector::Nmi, + connector::Noon, + connector::Nuvei, + connector::Opayo, + connector::Opennode, + connector::Payeezy, + connector::Payme, + connector::Paypal, + connector::Payu, + connector::Powertranz, + connector::Prophetpay, + connector::Rapyd, + connector::Signifyd, connector::Square, connector::Stax, connector::Stripe, diff --git a/crates/router/src/core/payments/flows/incremental_authorization_flow.rs b/crates/router/src/core/payments/flows/incremental_authorization_flow.rs new file mode 100644 index 000000000000..387916bab7c9 --- /dev/null +++ b/crates/router/src/core/payments/flows/incremental_authorization_flow.rs @@ -0,0 +1,118 @@ +use async_trait::async_trait; + +use super::ConstructFlowSpecificData; +use crate::{ + core::{ + errors::{ConnectorErrorExt, RouterResult}, + payments::{self, access_token, helpers, transformers, Feature, PaymentData}, + }, + routes::AppState, + services, + types::{self, api, domain}, +}; + +#[async_trait] +impl + ConstructFlowSpecificData< + api::IncrementalAuthorization, + types::PaymentsIncrementalAuthorizationData, + types::PaymentsResponseData, + > for PaymentData +{ + async fn construct_router_data<'a>( + &self, + state: &AppState, + connector_id: &str, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + customer: &Option, + merchant_connector_account: &helpers::MerchantConnectorAccountType, + ) -> RouterResult { + Box::pin(transformers::construct_payment_router_data::< + api::IncrementalAuthorization, + types::PaymentsIncrementalAuthorizationData, + >( + state, + self.clone(), + connector_id, + merchant_account, + key_store, + customer, + merchant_connector_account, + )) + .await + } +} + +#[async_trait] +impl Feature + for types::RouterData< + api::IncrementalAuthorization, + types::PaymentsIncrementalAuthorizationData, + types::PaymentsResponseData, + > +{ + async fn decide_flows<'a>( + self, + state: &AppState, + connector: &api::ConnectorData, + _customer: &Option, + call_connector_action: payments::CallConnectorAction, + _merchant_account: &domain::MerchantAccount, + connector_request: Option, + _key_store: &domain::MerchantKeyStore, + ) -> RouterResult { + let connector_integration: services::BoxedConnectorIntegration< + '_, + api::IncrementalAuthorization, + types::PaymentsIncrementalAuthorizationData, + types::PaymentsResponseData, + > = connector.connector.get_connector_integration(); + + let resp = services::execute_connector_processing_step( + state, + connector_integration, + &self, + call_connector_action, + connector_request, + ) + .await + .to_payment_failed_response()?; + + Ok(resp) + } + + async fn add_access_token<'a>( + &self, + state: &AppState, + connector: &api::ConnectorData, + merchant_account: &domain::MerchantAccount, + ) -> RouterResult { + access_token::add_access_token(state, connector, merchant_account, self).await + } + + async fn build_flow_specific_connector_request( + &mut self, + state: &AppState, + connector: &api::ConnectorData, + call_connector_action: payments::CallConnectorAction, + ) -> RouterResult<(Option, bool)> { + let request = match call_connector_action { + payments::CallConnectorAction::Trigger => { + let connector_integration: services::BoxedConnectorIntegration< + '_, + api::IncrementalAuthorization, + types::PaymentsIncrementalAuthorizationData, + types::PaymentsResponseData, + > = connector.connector.get_connector_integration(); + + connector_integration + .build_request(self, &state.conf.connectors) + .to_payment_failed_response()? + } + _ => None, + }; + + Ok((request, true)) + } +} diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 4d11f6400f44..4e491964e96c 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use api_models::payments::{CardToken, GetPaymentMethodType}; +use api_models::payments::{CardToken, GetPaymentMethodType, RequestSurchargeDetails}; use base64::Engine; use common_utils::{ ext_traits::{AsyncExt, ByteSliceExt, ValueExt}, @@ -572,6 +572,7 @@ pub fn validate_merchant_id( pub fn validate_request_amount_and_amount_to_capture( op_amount: Option, op_amount_to_capture: Option, + surcharge_details: Option, ) -> CustomResult<(), errors::ApiErrorResponse> { match (op_amount, op_amount_to_capture) { (None, _) => Ok(()), @@ -581,7 +582,11 @@ pub fn validate_request_amount_and_amount_to_capture( api::Amount::Value(amount_inner) => { // If both amount and amount to capture is present // then amount to be capture should be less than or equal to request amount - utils::when(!amount_to_capture.le(&amount_inner.get()), || { + let total_capturable_amount = amount_inner.get() + + surcharge_details + .map(|surcharge_details| surcharge_details.get_total_surcharge_amount()) + .unwrap_or(0); + utils::when(!amount_to_capture.le(&total_capturable_amount), || { Err(report!(errors::ApiErrorResponse::PreconditionFailed { message: format!( "amount_to_capture is greater than amount capture_amount: {amount_to_capture:?} request_amount: {amount:?}" @@ -603,13 +608,34 @@ pub fn validate_request_amount_and_amount_to_capture( /// if capture method = automatic, amount_to_capture(if provided) must be equal to amount #[instrument(skip_all)] -pub fn validate_amount_to_capture_in_create_call_request( +pub fn validate_amount_to_capture_and_capture_method( + payment_attempt: Option<&PaymentAttempt>, request: &api_models::payments::PaymentsRequest, ) -> CustomResult<(), errors::ApiErrorResponse> { - if request.capture_method.unwrap_or_default() == api_enums::CaptureMethod::Automatic { - let total_capturable_amount = request.get_total_capturable_amount(); - if let Some((amount_to_capture, total_capturable_amount)) = - request.amount_to_capture.zip(total_capturable_amount) + let capture_method = request + .capture_method + .or(payment_attempt + .map(|payment_attempt| payment_attempt.capture_method.unwrap_or_default())) + .unwrap_or_default(); + if capture_method == api_enums::CaptureMethod::Automatic { + let original_amount = request + .amount + .map(|amount| amount.into()) + .or(payment_attempt.map(|payment_attempt| payment_attempt.amount)); + let surcharge_amount = request + .surcharge_details + .map(|surcharge_details| surcharge_details.get_total_surcharge_amount()) + .or_else(|| { + payment_attempt.map(|payment_attempt| { + payment_attempt.surcharge_amount.unwrap_or(0) + + payment_attempt.tax_amount.unwrap_or(0) + }) + }) + .unwrap_or(0); + let total_capturable_amount = + original_amount.map(|original_amount| original_amount + surcharge_amount); + if let Some((total_capturable_amount, amount_to_capture)) = + total_capturable_amount.zip(request.amount_to_capture) { utils::when(amount_to_capture != total_capturable_amount, || { Err(report!(errors::ApiErrorResponse::PreconditionFailed { @@ -1354,7 +1380,6 @@ pub async fn retrieve_payment_method_with_temporary_token( state: &AppState, token: &str, payment_intent: &PaymentIntent, - card_cvc: Option>, merchant_key_store: &domain::MerchantKeyStore, card_token_data: Option<&CardToken>, ) -> RouterResult> { @@ -1395,10 +1420,13 @@ pub async fn retrieve_payment_method_with_temporary_token( updated_card.card_holder_name = name_on_card; } - if let Some(cvc) = card_cvc { - is_card_updated = true; - updated_card.card_cvc = cvc; + if let Some(token_data) = card_token_data { + if let Some(cvc) = token_data.card_cvc.clone() { + is_card_updated = true; + updated_card.card_cvc = cvc; + } } + if is_card_updated { let updated_pm = api::PaymentMethodData::Card(updated_card); vault::Vault::store_payment_method_data_in_locker( @@ -1444,7 +1472,6 @@ pub async fn retrieve_card_with_permanent_token( state: &AppState, token: &str, payment_intent: &PaymentIntent, - card_cvc: Option>, card_token_data: Option<&CardToken>, ) -> RouterResult { let customer_id = payment_intent @@ -1479,7 +1506,11 @@ pub async fn retrieve_card_with_permanent_token( card_holder_name: name_on_card.unwrap_or(masking::Secret::from("".to_string())), card_exp_month: card.card_exp_month, card_exp_year: card.card_exp_year, - card_cvc: card_cvc.unwrap_or_default(), + card_cvc: card_token_data + .cloned() + .unwrap_or_default() + .card_cvc + .unwrap_or_default(), card_issuer: card.card_brand, nick_name: card.nick_name.map(masking::Secret::new), card_network: None, @@ -1501,6 +1532,22 @@ pub async fn make_pm_data<'a, F: Clone, R, Ctx: PaymentMethodRetrieve>( Option, )> { let request = &payment_data.payment_method_data.clone(); + + let mut card_token_data = payment_data + .payment_method_data + .clone() + .and_then(|pmd| match pmd { + api_models::payments::PaymentMethodData::CardToken(token_data) => Some(token_data), + _ => None, + }) + .or(Some(CardToken::default())); + + if let Some(cvc) = payment_data.card_cvc.clone() { + if let Some(token_data) = card_token_data.as_mut() { + token_data.card_cvc = Some(cvc); + } + } + let token = payment_data.token.clone(); let hyperswitch_token = match payment_data.mandate_id { @@ -1560,13 +1607,6 @@ pub async fn make_pm_data<'a, F: Clone, R, Ctx: PaymentMethodRetrieve>( } }; - let card_cvc = payment_data.card_cvc.clone(); - - let card_token_data = request.as_ref().and_then(|pmd| match pmd { - api_models::payments::PaymentMethodData::CardToken(token_data) => Some(token_data), - _ => None, - }); - // TODO: Handle case where payment method and token both are present in request properly. let payment_method = match (request, hyperswitch_token) { (_, Some(hyperswitch_token)) => { @@ -1575,8 +1615,7 @@ pub async fn make_pm_data<'a, F: Clone, R, Ctx: PaymentMethodRetrieve>( merchant_key_store, &hyperswitch_token, &payment_data.payment_intent, - card_cvc, - card_token_data, + card_token_data.as_ref(), ) .await .attach_printable("in 'make_pm_data'")?; @@ -2378,6 +2417,20 @@ pub async fn get_merchant_fullfillment_time( } } +pub(crate) fn validate_payment_status_against_allowed_statuses( + intent_status: &storage_enums::IntentStatus, + allowed_statuses: &[storage_enums::IntentStatus], + action: &'static str, +) -> Result<(), errors::ApiErrorResponse> { + fp_utils::when(!allowed_statuses.contains(intent_status), || { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: format!( + "You cannot {action} this payment because it has status {intent_status}", + ), + }) + }) +} + pub(crate) fn validate_payment_status_against_not_allowed_statuses( intent_status: &storage_enums::IntentStatus, not_allowed_statuses: &[storage_enums::IntentStatus], @@ -2573,6 +2626,7 @@ mod tests { request_incremental_authorization: common_enums::RequestIncrementalAuthorization::default(), incremental_authorization_allowed: None, + authorization_count: None, }; let req_cs = Some("1".to_string()); let merchant_fulfillment_time = Some(900); @@ -2626,6 +2680,7 @@ mod tests { request_incremental_authorization: common_enums::RequestIncrementalAuthorization::default(), incremental_authorization_allowed: None, + authorization_count: None, }; let req_cs = Some("1".to_string()); let merchant_fulfillment_time = Some(10); @@ -2679,6 +2734,7 @@ mod tests { request_incremental_authorization: common_enums::RequestIncrementalAuthorization::default(), incremental_authorization_allowed: None, + authorization_count: None, }; let req_cs = Some("1".to_string()); let merchant_fulfillment_time = Some(10); @@ -3570,7 +3626,7 @@ impl ApplePayData { } pub fn get_key_params_for_surcharge_details( - payment_method_data: api_models::payments::PaymentMethodData, + payment_method_data: &api_models::payments::PaymentMethodData, ) -> RouterResult<( common_enums::PaymentMethod, common_enums::PaymentMethodType, @@ -3578,31 +3634,17 @@ pub fn get_key_params_for_surcharge_details( )> { match payment_method_data { api_models::payments::PaymentMethodData::Card(card) => { - let card_type = card - .card_type - .get_required_value("payment_method_data.card.card_type")?; let card_network = card .card_network + .clone() .get_required_value("payment_method_data.card.card_network")?; - match card_type.to_lowercase().as_str() { - "credit" => Ok(( - common_enums::PaymentMethod::Card, - common_enums::PaymentMethodType::Credit, - Some(card_network), - )), - "debit" => Ok(( - common_enums::PaymentMethod::Card, - common_enums::PaymentMethodType::Debit, - Some(card_network), - )), - _ => { - logger::debug!("Invalid Card type found in payment confirm call, hence surcharge not applicable"); - Err(errors::ApiErrorResponse::InvalidDataValue { - field_name: "payment_method_data.card.card_type", - } - .into()) - } - } + // surcharge generated will always be same for credit as well as debit + // since surcharge conditions cannot be defined on card_type + Ok(( + common_enums::PaymentMethod::Card, + common_enums::PaymentMethodType::Credit, + Some(card_network), + )) } api_models::payments::PaymentMethodData::CardRedirect(card_redirect_data) => Ok(( common_enums::PaymentMethod::CardRedirect, diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index 809c9e925de0..cf0c0ab294a8 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -10,6 +10,7 @@ pub mod payment_session; pub mod payment_start; pub mod payment_status; pub mod payment_update; +pub mod payments_incremental_authorization; use api_models::enums::FrmSuggestion; use async_trait::async_trait; @@ -22,6 +23,7 @@ pub use self::{ payment_create::PaymentCreate, payment_reject::PaymentReject, payment_response::PaymentResponse, payment_session::PaymentSession, payment_start::PaymentStart, payment_status::PaymentStatus, payment_update::PaymentUpdate, + payments_incremental_authorization::PaymentIncrementalAuthorization, }; use super::{helpers, CustomerDetails, PaymentData}; use crate::{ @@ -157,7 +159,6 @@ pub trait Domain: Send + Sync { &'a self, _state: &AppState, _payment_data: &mut PaymentData, - _request: &R, _merchant_account: &domain::MerchantAccount, ) -> CustomResult<(), errors::ApiErrorResponse> { Ok(()) diff --git a/crates/router/src/core/payments/operations/payment_approve.rs b/crates/router/src/core/payments/operations/payment_approve.rs index f51d7a93ee5e..37a3e1a14123 100644 --- a/crates/router/src/core/payments/operations/payment_approve.rs +++ b/crates/router/src/core/payments/operations/payment_approve.rs @@ -179,7 +179,11 @@ impl payment_intent.shipping_address_id = shipping_address.clone().map(|i| i.address_id); payment_intent.billing_address_id = billing_address.clone().map(|i| i.address_id); - payment_intent.return_url = request.return_url.as_ref().map(|a| a.to_string()); + payment_intent.return_url = request + .return_url + .as_ref() + .map(|a| a.to_string()) + .or(payment_intent.return_url); payment_intent.allowed_payment_method_types = request .get_allowed_payment_method_types_as_value() @@ -251,6 +255,8 @@ impl surcharge_details: None, frm_message: frm_response.ok(), payment_link_data: None, + incremental_authorization_details: None, + authorizations: vec![], }; let customer_details = Some(CustomerDetails { diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index ae7810971896..7c8fbcc34979 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -171,6 +171,8 @@ impl surcharge_details: None, frm_message: None, payment_link_data: None, + incremental_authorization_details: None, + authorizations: vec![], }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index 5b89cfdbcf0b..65b91f0401cf 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -215,6 +215,8 @@ impl surcharge_details: None, frm_message: None, payment_link_data: None, + incremental_authorization_details: None, + authorizations: vec![], }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index 8b264edbb3d1..48b503b96b0d 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -131,7 +131,9 @@ impl payment_attempt.browser_info = browser_info; payment_attempt.payment_method_type = payment_method_type.or(payment_attempt.payment_method_type); - payment_attempt.payment_experience = request.payment_experience; + payment_attempt.payment_experience = request + .payment_experience + .or(payment_attempt.payment_experience); currency = payment_attempt.currency.get_required_value("currency")?; amount = payment_attempt.amount.into(); @@ -173,7 +175,11 @@ impl payment_intent.shipping_address_id = shipping_address.clone().map(|i| i.address_id); payment_intent.billing_address_id = billing_address.clone().map(|i| i.address_id); - payment_intent.return_url = request.return_url.as_ref().map(|a| a.to_string()); + payment_intent.return_url = request + .return_url + .as_ref() + .map(|a| a.to_string()) + .or(payment_intent.return_url); payment_intent.allowed_payment_method_types = request .get_allowed_payment_method_types_as_value() @@ -245,6 +251,8 @@ impl surcharge_details: None, frm_message: None, payment_link_data: None, + incremental_authorization_details: None, + authorizations: vec![], }; let customer_details = Some(CustomerDetails { diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index d718db79a6d0..af2a9fa49c8b 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -5,7 +5,6 @@ use async_trait::async_trait; use common_utils::ext_traits::{AsyncExt, Encode}; use error_stack::{report, IntoReport, ResultExt}; use futures::FutureExt; -use redis_interface::errors::RedisError; use router_derive::PaymentOperation; use router_env::{instrument, tracing}; use tracing_futures::Instrument; @@ -19,7 +18,7 @@ use crate::{ self, helpers, operations, populate_surcharge_details, CustomerDetails, PaymentAddress, PaymentData, }, - utils::{self as core_utils, get_individual_surcharge_detail_from_redis}, + utils::{self as core_utils}, }, db::StorageInterface, routes::AppState, @@ -439,12 +438,20 @@ impl sm }); - Self::validate_request_surcharge_details_with_session_surcharge_details( - state, - &payment_attempt, - request, - ) - .await?; + let additional_pm_data = request + .payment_method_data + .as_ref() + .async_map(|payment_method_data| async { + helpers::get_additional_payment_data(payment_method_data, &*state.store).await + }) + .await; + let payment_method_data_after_card_bin_call = request + .payment_method_data + .as_ref() + .zip(additional_pm_data) + .map(|(payment_method_data, additional_payment_data)| { + payment_method_data.apply_additional_payment_data(additional_payment_data) + }); let payment_data = PaymentData { flow: PhantomData, @@ -462,7 +469,7 @@ impl billing: billing_address.as_ref().map(|a| a.into()), }, confirm: request.confirm, - payment_method_data: request.payment_method_data.clone(), + payment_method_data: payment_method_data_after_card_bin_call, force_sync: None, refunds: vec![], disputes: vec![], @@ -479,6 +486,8 @@ impl surcharge_details: None, frm_message: None, payment_link_data: None, + incremental_authorization_details: None, + authorizations: vec![], }; let get_trackers_response = operations::GetTrackerResponse { @@ -591,10 +600,9 @@ impl Domain, - request: &api::PaymentsRequest, _merchant_account: &domain::MerchantAccount, ) -> CustomResult<(), errors::ApiErrorResponse> { - populate_surcharge_details(state, payment_data, request).await + populate_surcharge_details(state, payment_data).await } } @@ -888,70 +896,3 @@ impl ValidateRequest RouterResult<()> { - match ( - request.surcharge_details, - request.payment_method_data.as_ref(), - ) { - (Some(request_surcharge_details), Some(payment_method_data)) => { - if let Some(payment_method_type) = - payment_method_data.get_payment_method_type_if_session_token_type() - { - let invalid_surcharge_details_error = Err(errors::ApiErrorResponse::InvalidRequestData { - message: "surcharge_details sent in session token flow doesn't match with the one sent in confirm request".into(), - }.into()); - if let Some(attempt_surcharge_amount) = payment_attempt.surcharge_amount { - // payment_attempt.surcharge_amount will be Some if some surcharge was sent in payment create - // if surcharge was sent in payment create call, the same would have been sent to the connector during session call - // So verify the same - if request_surcharge_details.surcharge_amount != attempt_surcharge_amount - || request_surcharge_details.tax_amount != payment_attempt.tax_amount - { - return invalid_surcharge_details_error; - } - } else { - // if not sent in payment create - // verify that any calculated surcharge sent in session flow is same as the one sent in confirm - return match get_individual_surcharge_detail_from_redis( - state, - &payment_method_type.into(), - &payment_method_type, - None, - &payment_attempt.attempt_id, - ) - .await - { - Ok(surcharge_details) => utils::when( - !surcharge_details - .is_request_surcharge_matching(request_surcharge_details), - || invalid_surcharge_details_error, - ), - Err(err) if err.current_context() == &RedisError::NotFound => { - utils::when(!request_surcharge_details.is_surcharge_zero(), || { - invalid_surcharge_details_error - }) - } - Err(err) => Err(err) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to fetch redis value"), - }; - } - } - Ok(()) - } - (Some(_request_surcharge_details), None) => { - Err(errors::ApiErrorResponse::MissingRequiredField { - field_name: "payment_method_data", - } - .into()) - } - _ => Ok(()), - } - } -} diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index ac387076d1d1..eb7f31ba24d1 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -167,7 +167,7 @@ impl ) .await?; - let payment_attempt_new = Self::make_payment_attempt( + let (payment_attempt_new, additional_payment_data) = Self::make_payment_attempt( &payment_id, merchant_id, money, @@ -286,10 +286,18 @@ impl // The operation merges mandate data from both request and payment_attempt let setup_mandate = setup_mandate.map(MandateData::from); - let surcharge_details = request.surcharge_details.map(|surcharge_details| { - surcharge_details.get_surcharge_details_object(payment_attempt.amount) + let surcharge_details = request.surcharge_details.map(|request_surcharge_details| { + payments::types::SurchargeDetails::from((&request_surcharge_details, &payment_attempt)) }); + let payment_method_data_after_card_bin_call = request + .payment_method_data + .as_ref() + .zip(additional_payment_data) + .map(|(payment_method_data, additional_payment_data)| { + payment_method_data.apply_additional_payment_data(additional_payment_data) + }); + let payment_data = PaymentData { flow: PhantomData, payment_intent, @@ -306,7 +314,7 @@ impl billing: billing_address.as_ref().map(|a| a.into()), }, confirm: request.confirm, - payment_method_data: request.payment_method_data.clone(), + payment_method_data: payment_method_data_after_card_bin_call, refunds: vec![], disputes: vec![], attempts: None, @@ -323,6 +331,8 @@ impl surcharge_details, frm_message: None, payment_link_data, + incremental_authorization_details: None, + authorizations: vec![], }; let get_trackers_response = operations::GetTrackerResponse { @@ -540,14 +550,14 @@ impl ValidateRequest, state: &AppState, - ) -> RouterResult { + ) -> RouterResult<( + storage::PaymentAttemptNew, + Option, + )> { let created_at @ modified_at @ last_synced = Some(common_utils::date_time::now()); let status = helpers::payment_attempt_status_fsm(&request.payment_method_data, request.confirm); @@ -614,7 +627,8 @@ impl PaymentCreate { .async_map(|payment_method_data| async { helpers::get_additional_payment_data(payment_method_data, &*state.store).await }) - .await + .await; + let additional_pm_data_value = additional_pm_data .as_ref() .map(Encode::::encode_to_value) .transpose() @@ -629,35 +643,38 @@ impl PaymentCreate { utils::get_payment_attempt_id(payment_id, 1) }; - Ok(storage::PaymentAttemptNew { - payment_id: payment_id.to_string(), - merchant_id: merchant_id.to_string(), - attempt_id, - status, - currency, - amount: amount.into(), - payment_method, - capture_method: request.capture_method, - capture_on: request.capture_on, - confirm: request.confirm.unwrap_or(false), - created_at, - modified_at, - last_synced, - authentication_type: request.authentication_type, - browser_info, - payment_experience: request.payment_experience, - payment_method_type, - payment_method_data: additional_pm_data, - amount_to_capture: request.amount_to_capture, - payment_token: request.payment_token.clone(), - mandate_id: request.mandate_id.clone(), - business_sub_label: request.business_sub_label.clone(), - mandate_details: request - .mandate_data - .as_ref() - .and_then(|inner| inner.mandate_type.clone().map(Into::into)), - ..storage::PaymentAttemptNew::default() - }) + Ok(( + storage::PaymentAttemptNew { + payment_id: payment_id.to_string(), + merchant_id: merchant_id.to_string(), + attempt_id, + status, + currency, + amount: amount.into(), + payment_method, + capture_method: request.capture_method, + capture_on: request.capture_on, + confirm: request.confirm.unwrap_or(false), + created_at, + modified_at, + last_synced, + authentication_type: request.authentication_type, + browser_info, + payment_experience: request.payment_experience, + payment_method_type, + payment_method_data: additional_pm_data_value, + amount_to_capture: request.amount_to_capture, + payment_token: request.payment_token.clone(), + mandate_id: request.mandate_id.clone(), + business_sub_label: request.business_sub_label.clone(), + mandate_details: request + .mandate_data + .as_ref() + .and_then(|inner| inner.mandate_type.clone().map(Into::into)), + ..storage::PaymentAttemptNew::default() + }, + additional_pm_data, + )) } #[instrument(skip_all)] @@ -757,6 +774,7 @@ impl PaymentCreate { updated_by: merchant_account.storage_scheme.to_string(), request_incremental_authorization, incremental_authorization_allowed: None, + authorization_count: None, }) } diff --git a/crates/router/src/core/payments/operations/payment_reject.rs b/crates/router/src/core/payments/operations/payment_reject.rs index ae606187a0a1..03bf6dd46b60 100644 --- a/crates/router/src/core/payments/operations/payment_reject.rs +++ b/crates/router/src/core/payments/operations/payment_reject.rs @@ -158,6 +158,8 @@ impl surcharge_details: None, frm_message: frm_response.ok(), payment_link_data: None, + incremental_authorization_details: None, + authorizations: vec![], }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 9781ad651ee2..f92487d74a7b 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -1,8 +1,9 @@ use std::collections::HashMap; use async_trait::async_trait; +use common_enums::AuthorizationStatus; use data_models::payments::payment_attempt::PaymentAttempt; -use error_stack::ResultExt; +use error_stack::{report, IntoReport, ResultExt}; use futures::FutureExt; use router_derive; use router_env::{instrument, tracing}; @@ -36,7 +37,7 @@ use crate::{ #[derive(Debug, Clone, Copy, router_derive::PaymentOperation)] #[operation( operations = "post_update_tracker", - flow = "sync_data, authorize_data, cancel_data, capture_data, complete_authorize_data, approve_data, reject_data, setup_mandate_data, session_data" + flow = "sync_data, authorize_data, cancel_data, capture_data, complete_authorize_data, approve_data, reject_data, setup_mandate_data, session_data,incremental_authorization_data" )] pub struct PaymentResponse; @@ -76,6 +77,138 @@ impl PostUpdateTracker, types::PaymentsAuthorizeData } } +#[async_trait] +impl PostUpdateTracker, types::PaymentsIncrementalAuthorizationData> + for PaymentResponse +{ + async fn update_tracker<'b>( + &'b self, + db: &'b AppState, + _payment_id: &api::PaymentIdType, + mut payment_data: PaymentData, + router_data: types::RouterData< + F, + types::PaymentsIncrementalAuthorizationData, + types::PaymentsResponseData, + >, + storage_scheme: enums::MerchantStorageScheme, + ) -> RouterResult> + where + F: 'b + Send, + { + let incremental_authorization_details = payment_data + .incremental_authorization_details + .clone() + .ok_or_else(|| { + report!(errors::ApiErrorResponse::InternalServerError) + .attach_printable("missing incremental_authorization_details in payment_data") + })?; + // Update payment_intent and payment_attempt 'amount' if incremental_authorization is successful + let (option_payment_attempt_update, option_payment_intent_update) = + match router_data.response.clone() { + Err(_) => (None, None), + Ok(types::PaymentsResponseData::IncrementalAuthorizationResponse { + status, + .. + }) => { + if status == AuthorizationStatus::Success { + (Some( + storage::PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { + amount: incremental_authorization_details.total_amount, + amount_capturable: incremental_authorization_details.total_amount, + }, + ), Some( + storage::PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { + amount: incremental_authorization_details.total_amount, + }, + )) + } else { + (None, None) + } + } + _ => Err(errors::ApiErrorResponse::InternalServerError) + .into_report() + .attach_printable("unexpected response in incremental_authorization flow")?, + }; + //payment_attempt update + if let Some(payment_attempt_update) = option_payment_attempt_update { + payment_data.payment_attempt = db + .store + .update_payment_attempt_with_attempt_id( + payment_data.payment_attempt.clone(), + payment_attempt_update, + storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + } + // payment_intent update + if let Some(payment_intent_update) = option_payment_intent_update { + payment_data.payment_intent = db + .store + .update_payment_intent( + payment_data.payment_intent.clone(), + payment_intent_update, + storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + } + // Update the status of authorization record + let authorization_update = match &router_data.response { + Err(res) => Ok(storage::AuthorizationUpdate::StatusUpdate { + status: AuthorizationStatus::Failure, + error_code: Some(res.code.clone()), + error_message: Some(res.message.clone()), + connector_authorization_id: None, + }), + Ok(types::PaymentsResponseData::IncrementalAuthorizationResponse { + status, + error_code, + error_message, + connector_authorization_id, + }) => Ok(storage::AuthorizationUpdate::StatusUpdate { + status: status.clone(), + error_code: error_code.clone(), + error_message: error_message.clone(), + connector_authorization_id: connector_authorization_id.clone(), + }), + Ok(_) => Err(errors::ApiErrorResponse::InternalServerError) + .into_report() + .attach_printable("unexpected response in incremental_authorization flow"), + }?; + let authorization_id = incremental_authorization_details + .authorization_id + .clone() + .ok_or( + report!(errors::ApiErrorResponse::InternalServerError).attach_printable( + "missing authorization_id in incremental_authorization_details in payment_data", + ), + )?; + db.store + .update_authorization_by_merchant_id_authorization_id( + router_data.merchant_id.clone(), + authorization_id, + authorization_update, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed while updating authorization")?; + //Fetch all the authorizations of the payment and send in incremental authorization response + let authorizations = db + .store + .find_all_authorizations_by_merchant_id_payment_id( + &router_data.merchant_id, + &payment_data.payment_intent.payment_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed while retrieving authorizations")?; + payment_data.authorizations = authorizations; + Ok(payment_data) + } +} + #[async_trait] impl PostUpdateTracker, types::PaymentsSyncData> for PaymentResponse { async fn update_tracker<'b>( @@ -544,6 +677,7 @@ async fn payment_response_update_tracker( types::PaymentsResponseData::TokenizationResponse { .. } => (None, None), types::PaymentsResponseData::ConnectorCustomerResponse { .. } => (None, None), types::PaymentsResponseData::ThreeDSEnrollmentResponse { .. } => (None, None), + types::PaymentsResponseData::IncrementalAuthorizationResponse { .. } => (None, None), types::PaymentsResponseData::MultipleCaptureResponse { capture_sync_response_list, } => match payment_data.multiple_capture_data { @@ -637,6 +771,7 @@ async fn payment_response_update_tracker( payment_data.payment_attempt.status, ), updated_by: storage_scheme.to_string(), + // make this false only if initial payment fails, if incremental authorization call fails don't make it false incremental_authorization_allowed: Some(false), }, Ok(_) => storage::PaymentIntentUpdate::ResponseUpdate { diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index 6097a5e430ce..572bc710b963 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -195,6 +195,8 @@ impl surcharge_details: None, frm_message: None, payment_link_data: None, + incremental_authorization_details: None, + authorizations: vec![], }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index 3a4ae2c2e0de..887edd030d13 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -169,6 +169,8 @@ impl surcharge_details: None, frm_message: None, payment_link_data: None, + incremental_authorization_details: None, + authorizations: vec![], }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index d0cd4b32d3c2..0320cf50663e 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -314,6 +314,20 @@ async fn get_tracker_for_sync< ) })?; + let authorizations = db + .find_all_authorizations_by_merchant_id_payment_id( + &merchant_account.merchant_id, + &payment_id_str, + ) + .await + .change_context(errors::ApiErrorResponse::PaymentNotFound) + .attach_printable_lazy(|| { + format!( + "Failed while getting authorizations list for, payment_id: {}, merchant_id: {}", + &payment_id_str, merchant_account.merchant_id + ) + })?; + let disputes = db .find_disputes_by_merchant_id_payment_id(&merchant_account.merchant_id, &payment_id_str) .await @@ -407,6 +421,8 @@ async fn get_tracker_for_sync< payment_link_data: None, surcharge_details: None, frm_message: frm_response.ok(), + incremental_authorization_details: None, + authorizations, }; let get_trackers_response = operations::GetTrackerResponse { diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index 1176eeb1dd3f..f1a35cffce87 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -21,7 +21,7 @@ use crate::{ types::{ api::{self, PaymentIdTypeExt}, domain, - storage::{self, enums as storage_enums}, + storage::{self, enums as storage_enums, payment_attempt::PaymentAttemptExt}, }, utils::OptionExt, }; @@ -80,6 +80,7 @@ impl &[ storage_enums::IntentStatus::Failed, storage_enums::IntentStatus::Succeeded, + storage_enums::IntentStatus::PartiallyCaptured, storage_enums::IntentStatus::RequiresCapture, ], "update", @@ -134,6 +135,20 @@ impl .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + helpers::validate_amount_to_capture_and_capture_method(Some(&payment_attempt), request)?; + + helpers::validate_request_amount_and_amount_to_capture( + request.amount, + request.amount_to_capture, + request + .surcharge_details + .or(payment_attempt.get_surcharge_details()), + ) + .change_context(errors::ApiErrorResponse::InvalidDataFormat { + field_name: "amount_to_capture".to_string(), + expected_format: "amount_to_capture lesser than or equal to amount".to_string(), + })?; + currency = request .currency .or(payment_attempt.currency) @@ -322,7 +337,7 @@ impl })?; let surcharge_details = request.surcharge_details.map(|request_surcharge_details| { - request_surcharge_details.get_surcharge_details_object(payment_attempt.amount) + payments::types::SurchargeDetails::from((&request_surcharge_details, &payment_attempt)) }); let payment_data = PaymentData { @@ -358,6 +373,8 @@ impl surcharge_details, frm_message: None, payment_link_data: None, + incremental_authorization_details: None, + authorizations: vec![], }; let get_trackers_response = operations::GetTrackerResponse { @@ -629,6 +646,7 @@ impl ValidateRequest + GetTracker, PaymentsIncrementalAuthorizationRequest, Ctx> + for PaymentIncrementalAuthorization +{ + #[instrument(skip_all)] + async fn get_trackers<'a>( + &'a self, + state: &'a AppState, + payment_id: &api::PaymentIdType, + request: &PaymentsIncrementalAuthorizationRequest, + _mandate_type: Option, + merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, + _auth_flow: services::AuthFlow, + ) -> RouterResult< + operations::GetTrackerResponse<'a, F, PaymentsIncrementalAuthorizationRequest, Ctx>, + > { + let db = &*state.store; + let merchant_id = &merchant_account.merchant_id; + let storage_scheme = merchant_account.storage_scheme; + let payment_id = payment_id + .get_payment_intent_id() + .change_context(errors::ApiErrorResponse::PaymentNotFound)?; + + let payment_intent = db + .find_payment_intent_by_payment_id_merchant_id(&payment_id, merchant_id, storage_scheme) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + helpers::validate_payment_status_against_allowed_statuses( + &payment_intent.status, + &[enums::IntentStatus::RequiresCapture], + "increment authorization", + )?; + + if payment_intent.incremental_authorization_allowed != Some(true) { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: + "You cannot increment authorization this payment because it is not allowed for incremental_authorization".to_owned(), + })? + } + + if request.amount < payment_intent.amount { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Amount should be greater than original authorized amount".to_owned(), + })? + } + + let attempt_id = payment_intent.active_attempt.get_id().clone(); + let payment_attempt = db + .find_payment_attempt_by_payment_id_merchant_id_attempt_id( + payment_intent.payment_id.as_str(), + merchant_id, + attempt_id.clone().as_str(), + storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + let currency = payment_attempt.currency.get_required_value("currency")?; + let amount = payment_attempt.amount; + + let profile_id = payment_intent + .profile_id + .as_ref() + .get_required_value("profile_id") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("'profile_id' not set in payment intent")?; + + let business_profile = state + .store + .find_business_profile_by_profile_id(profile_id) + .await + .to_not_found_response(errors::ApiErrorResponse::BusinessProfileNotFound { + id: profile_id.to_string(), + })?; + + let payment_data = payments::PaymentData { + flow: PhantomData, + payment_intent, + payment_attempt, + currency, + amount: amount.into(), + email: None, + mandate_id: None, + mandate_connector: None, + setup_mandate: None, + token: None, + address: PaymentAddress { + billing: None, + shipping: None, + }, + confirm: None, + payment_method_data: None, + force_sync: None, + refunds: vec![], + disputes: vec![], + attempts: None, + sessions_token: vec![], + card_cvc: None, + creds_identifier: None, + pm_token: None, + connector_customer_id: None, + recurring_mandate_payment_data: None, + ephemeral_key: None, + multiple_capture_data: None, + redirect_response: None, + surcharge_details: None, + frm_message: None, + payment_link_data: None, + incremental_authorization_details: Some(IncrementalAuthorizationDetails { + additional_amount: request.amount - amount, + total_amount: request.amount, + reason: request.reason.clone(), + authorization_id: None, + }), + authorizations: vec![], + }; + + let get_trackers_response = operations::GetTrackerResponse { + operation: Box::new(self), + customer_details: None, + payment_data, + business_profile, + }; + + Ok(get_trackers_response) + } +} + +#[async_trait] +impl + UpdateTracker, PaymentsIncrementalAuthorizationRequest, Ctx> + for PaymentIncrementalAuthorization +{ + #[instrument(skip_all)] + async fn update_trackers<'b>( + &'b self, + db: &'b AppState, + mut payment_data: payments::PaymentData, + _customer: Option, + storage_scheme: enums::MerchantStorageScheme, + _updated_customer: Option, + _mechant_key_store: &domain::MerchantKeyStore, + _frm_suggestion: Option, + _header_payload: api::HeaderPayload, + ) -> RouterResult<( + BoxedOperation<'b, F, PaymentsIncrementalAuthorizationRequest, Ctx>, + payments::PaymentData, + )> + where + F: 'b + Send, + { + let new_authorization_count = payment_data + .payment_intent + .authorization_count + .map(|count| count + 1) + .unwrap_or(1); + // Create new authorization record + let authorization_new = AuthorizationNew { + authorization_id: format!( + "{}_{}", + common_utils::generate_id_with_default_len("auth"), + new_authorization_count + ), + merchant_id: payment_data.payment_intent.merchant_id.clone(), + payment_id: payment_data.payment_intent.payment_id.clone(), + amount: payment_data + .incremental_authorization_details + .clone() + .map(|details| details.total_amount) + .ok_or( + report!(errors::ApiErrorResponse::InternalServerError).attach_printable( + "missing incremental_authorization_details in payment_data", + ), + )?, + status: common_enums::AuthorizationStatus::Processing, + error_code: None, + error_message: None, + connector_authorization_id: None, + previously_authorized_amount: payment_data.payment_intent.amount, + }; + let authorization = db + .store + .insert_authorization(authorization_new.clone()) + .await + .to_duplicate_response(errors::ApiErrorResponse::GenericDuplicateError { + message: format!( + "Authorization with authorization_id {} already exists", + authorization_new.authorization_id + ), + }) + .attach_printable("failed while inserting new authorization")?; + // Update authorization_count in payment_intent + payment_data.payment_intent = db + .store + .update_payment_intent( + payment_data.payment_intent.clone(), + storage::PaymentIntentUpdate::AuthorizationCountUpdate { + authorization_count: new_authorization_count, + }, + storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) + .attach_printable("Failed to update authorization_count in Payment Intent")?; + match &payment_data.incremental_authorization_details { + Some(details) => { + payment_data.incremental_authorization_details = + Some(IncrementalAuthorizationDetails { + authorization_id: Some(authorization.authorization_id), + ..details.clone() + }); + } + None => Err(errors::ApiErrorResponse::InternalServerError) + .into_report() + .attach_printable("missing incremental_authorization_details in payment_data")?, + } + Ok((Box::new(self), payment_data)) + } +} + +impl + ValidateRequest + for PaymentIncrementalAuthorization +{ + #[instrument(skip_all)] + fn validate_request<'a, 'b>( + &'b self, + request: &PaymentsIncrementalAuthorizationRequest, + merchant_account: &'a domain::MerchantAccount, + ) -> RouterResult<( + BoxedOperation<'b, F, PaymentsIncrementalAuthorizationRequest, Ctx>, + operations::ValidateResult<'a>, + )> { + Ok(( + Box::new(self), + operations::ValidateResult { + merchant_id: &merchant_account.merchant_id, + payment_id: api::PaymentIdType::PaymentIntentId(request.payment_id.to_owned()), + mandate_type: None, + storage_scheme: merchant_account.storage_scheme, + requeue: false, + }, + )) + } +} + +#[async_trait] +impl + Domain for PaymentIncrementalAuthorization +{ + #[instrument(skip_all)] + async fn get_or_create_customer_details<'a>( + &'a self, + _db: &dyn StorageInterface, + _payment_data: &mut payments::PaymentData, + _request: Option, + _merchant_key_store: &domain::MerchantKeyStore, + ) -> CustomResult< + ( + BoxedOperation<'a, F, PaymentsIncrementalAuthorizationRequest, Ctx>, + Option, + ), + errors::StorageError, + > { + Ok((Box::new(self), None)) + } + + #[instrument(skip_all)] + async fn make_pm_data<'a>( + &'a self, + _state: &'a AppState, + _payment_data: &mut payments::PaymentData, + _storage_scheme: enums::MerchantStorageScheme, + _merchant_key_store: &domain::MerchantKeyStore, + ) -> RouterResult<( + BoxedOperation<'a, F, PaymentsIncrementalAuthorizationRequest, Ctx>, + Option, + )> { + Ok((Box::new(self), None)) + } + + async fn get_connector<'a>( + &'a self, + _merchant_account: &domain::MerchantAccount, + state: &AppState, + _request: &PaymentsIncrementalAuthorizationRequest, + _payment_intent: &storage::PaymentIntent, + _merchant_key_store: &domain::MerchantKeyStore, + ) -> CustomResult { + helpers::get_connector_default(state, None).await + } +} diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 51e139c97988..bd6d03e5625a 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -4,7 +4,7 @@ use api_models::payments::{FrmMessage, RequestSurchargeDetails}; use common_enums::RequestIncrementalAuthorization; use common_utils::{consts::X_HS_LATENCY, fp_utils}; use diesel_models::ephemeral_key; -use error_stack::{IntoReport, ResultExt}; +use error_stack::{report, IntoReport, ResultExt}; use router_env::{instrument, tracing}; use super::{flows::Feature, PaymentData}; @@ -392,6 +392,18 @@ where ) }; + let incremental_authorizations_response = if payment_data.authorizations.is_empty() { + None + } else { + Some( + payment_data + .authorizations + .into_iter() + .map(ForeignInto::foreign_into) + .collect(), + ) + }; + let attempts_response = payment_data.attempts.map(|attempts| { attempts .into_iter() @@ -692,6 +704,8 @@ where .set_incremental_authorization_allowed( payment_intent.incremental_authorization_allowed, ) + .set_authorization_count(payment_intent.authorization_count) + .set_incremental_authorizations(incremental_authorizations_response) .to_owned(), headers, )) @@ -755,6 +769,8 @@ where unified_code: payment_attempt.unified_code, unified_message: payment_attempt.unified_message, incremental_authorization_allowed: payment_intent.incremental_authorization_allowed, + authorization_count: payment_intent.authorization_count, + incremental_authorizations: incremental_authorizations_response, ..Default::default() }, headers, @@ -1078,6 +1094,50 @@ impl TryFrom> for types::PaymentsSyncData } } +impl TryFrom> + for types::PaymentsIncrementalAuthorizationData +{ + type Error = error_stack::Report; + + fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { + let payment_data = additional_data.payment_data; + let connector = api::ConnectorData::get_connector_by_name( + &additional_data.state.conf.connectors, + &additional_data.connector_name, + api::GetToken::Connector, + payment_data.payment_attempt.merchant_connector_id.clone(), + )?; + Ok(Self { + total_amount: payment_data + .incremental_authorization_details + .clone() + .map(|details| details.total_amount) + .ok_or( + report!(errors::ApiErrorResponse::InternalServerError).attach_printable( + "missing incremental_authorization_details in payment_data", + ), + )?, + additional_amount: payment_data + .incremental_authorization_details + .clone() + .map(|details| details.additional_amount) + .ok_or( + report!(errors::ApiErrorResponse::InternalServerError).attach_printable( + "missing incremental_authorization_details in payment_data", + ), + )?, + reason: payment_data + .incremental_authorization_details + .and_then(|details| details.reason), + currency: payment_data.currency, + connector_transaction_id: connector + .connector + .connector_transaction_id(payment_data.payment_attempt.clone())? + .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?, + }) + } +} + impl api::ConnectorTransactionId for Helcim { fn connector_transaction_id( &self, diff --git a/crates/router/src/core/payments/types.rs b/crates/router/src/core/payments/types.rs index 5e150a33d5c5..001082d2c92e 100644 --- a/crates/router/src/core/payments/types.rs +++ b/crates/router/src/core/payments/types.rs @@ -1,10 +1,21 @@ -use std::collections::HashMap; +use std::{collections::HashMap, num::TryFromIntError}; +use api_models::{payment_methods::SurchargeDetailsResponse, payments::RequestSurchargeDetails}; +use common_utils::{consts, errors::CustomResult, ext_traits::Encode, types as common_types}; +use data_models::payments::payment_attempt::PaymentAttempt; use error_stack::{IntoReport, ResultExt}; +use redis_interface::errors::RedisError; +use router_env::{instrument, tracing}; use crate::{ + consts as router_consts, core::errors::{self, RouterResult}, - types::storage::{self, enums as storage_enums}, + routes::AppState, + types::{ + domain, + storage::{self, enums as storage_enums}, + transformers::ForeignTryFrom, + }, }; #[derive(Clone, Debug)] @@ -164,3 +175,193 @@ impl MultipleCaptureData { .collect() } } + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct SurchargeDetails { + /// surcharge value + pub surcharge: common_types::Surcharge, + /// tax on surcharge value + pub tax_on_surcharge: + Option>, + /// surcharge amount for this payment + pub surcharge_amount: i64, + /// tax on surcharge amount for this payment + pub tax_on_surcharge_amount: i64, + /// sum of original amount, + pub final_amount: i64, +} + +impl From<(&RequestSurchargeDetails, &PaymentAttempt)> for SurchargeDetails { + fn from( + (request_surcharge_details, payment_attempt): (&RequestSurchargeDetails, &PaymentAttempt), + ) -> Self { + let surcharge_amount = request_surcharge_details.surcharge_amount; + let tax_on_surcharge_amount = request_surcharge_details.tax_amount.unwrap_or(0); + Self { + surcharge: common_types::Surcharge::Fixed(request_surcharge_details.surcharge_amount), + tax_on_surcharge: None, + surcharge_amount, + tax_on_surcharge_amount, + final_amount: payment_attempt.amount + surcharge_amount + tax_on_surcharge_amount, + } + } +} + +impl ForeignTryFrom<(&SurchargeDetails, &PaymentAttempt)> for SurchargeDetailsResponse { + type Error = TryFromIntError; + fn foreign_try_from( + (surcharge_details, payment_attempt): (&SurchargeDetails, &PaymentAttempt), + ) -> Result { + let currency = payment_attempt.currency.unwrap_or_default(); + let display_surcharge_amount = + currency.to_currency_base_unit_asf64(surcharge_details.surcharge_amount)?; + let display_tax_on_surcharge_amount = + currency.to_currency_base_unit_asf64(surcharge_details.tax_on_surcharge_amount)?; + let display_final_amount = + currency.to_currency_base_unit_asf64(surcharge_details.final_amount)?; + Ok(Self { + surcharge: surcharge_details.surcharge.clone().into(), + tax_on_surcharge: surcharge_details.tax_on_surcharge.clone().map(Into::into), + display_surcharge_amount, + display_tax_on_surcharge_amount, + display_total_surcharge_amount: display_surcharge_amount + + display_tax_on_surcharge_amount, + display_final_amount, + }) + } +} + +impl SurchargeDetails { + pub fn is_request_surcharge_matching( + &self, + request_surcharge_details: RequestSurchargeDetails, + ) -> bool { + request_surcharge_details.surcharge_amount == self.surcharge_amount + && request_surcharge_details.tax_amount.unwrap_or(0) == self.tax_on_surcharge_amount + } + pub fn get_total_surcharge_amount(&self) -> i64 { + self.surcharge_amount + self.tax_on_surcharge_amount + } +} + +#[derive(Eq, Hash, PartialEq, Clone, Debug, strum::Display)] +pub enum SurchargeKey { + Token(String), + PaymentMethodData( + common_enums::PaymentMethod, + common_enums::PaymentMethodType, + Option, + ), +} + +#[derive(Clone, Debug)] +pub struct SurchargeMetadata { + surcharge_results: HashMap, + pub payment_attempt_id: String, +} + +impl SurchargeMetadata { + pub fn new(payment_attempt_id: String) -> Self { + Self { + surcharge_results: HashMap::new(), + payment_attempt_id, + } + } + pub fn is_empty_result(&self) -> bool { + self.surcharge_results.is_empty() + } + pub fn get_surcharge_results_size(&self) -> usize { + self.surcharge_results.len() + } + pub fn insert_surcharge_details( + &mut self, + surcharge_key: SurchargeKey, + surcharge_details: SurchargeDetails, + ) { + self.surcharge_results + .insert(surcharge_key, surcharge_details); + } + pub fn get_surcharge_details(&self, surcharge_key: SurchargeKey) -> Option<&SurchargeDetails> { + self.surcharge_results.get(&surcharge_key) + } + pub fn get_surcharge_metadata_redis_key(payment_attempt_id: &str) -> String { + format!("surcharge_metadata_{}", payment_attempt_id) + } + pub fn get_individual_surcharge_key_value_pairs(&self) -> Vec<(String, SurchargeDetails)> { + self.surcharge_results + .iter() + .map(|(surcharge_key, surcharge_details)| { + let key = Self::get_surcharge_details_redis_hashset_key(surcharge_key); + (key, surcharge_details.to_owned()) + }) + .collect() + } + pub fn get_surcharge_details_redis_hashset_key(surcharge_key: &SurchargeKey) -> String { + match surcharge_key { + SurchargeKey::Token(token) => { + format!("token_{}", token) + } + SurchargeKey::PaymentMethodData(payment_method, payment_method_type, card_network) => { + if let Some(card_network) = card_network { + format!( + "{}_{}_{}", + payment_method, payment_method_type, card_network + ) + } else { + format!("{}_{}", payment_method, payment_method_type) + } + } + } + } + #[instrument(skip_all)] + pub async fn persist_individual_surcharge_details_in_redis( + &self, + state: &AppState, + merchant_account: &domain::MerchantAccount, + ) -> RouterResult<()> { + if !self.is_empty_result() { + let redis_conn = state + .store + .get_redis_conn() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get redis connection")?; + let redis_key = Self::get_surcharge_metadata_redis_key(&self.payment_attempt_id); + + let mut value_list = Vec::with_capacity(self.get_surcharge_results_size()); + for (key, value) in self.get_individual_surcharge_key_value_pairs().into_iter() { + value_list.push(( + key, + Encode::::encode_to_string_of_json(&value) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to encode to string of json")?, + )); + } + let intent_fulfillment_time = merchant_account + .intent_fulfillment_time + .unwrap_or(router_consts::DEFAULT_FULFILLMENT_TIME); + redis_conn + .set_hash_fields(&redis_key, value_list, Some(intent_fulfillment_time)) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to write to redis")?; + } + Ok(()) + } + + #[instrument(skip_all)] + pub async fn get_individual_surcharge_detail_from_redis( + state: &AppState, + surcharge_key: SurchargeKey, + payment_attempt_id: &str, + ) -> CustomResult { + let redis_conn = state + .store + .get_redis_conn() + .attach_printable("Failed to get redis connection")?; + let redis_key = Self::get_surcharge_metadata_redis_key(payment_attempt_id); + let value_key = Self::get_surcharge_details_redis_hashset_key(&surcharge_key); + redis_conn + .get_hash_field_and_deserialize(&redis_key, &value_key, "SurchargeDetails") + .await + } +} diff --git a/crates/router/src/core/pm_auth.rs b/crates/router/src/core/pm_auth.rs new file mode 100644 index 000000000000..821f049d8cfc --- /dev/null +++ b/crates/router/src/core/pm_auth.rs @@ -0,0 +1,729 @@ +use std::{collections::HashMap, str::FromStr}; + +use api_models::{ + enums, + payment_methods::{self, BankAccountAccessCreds}, + payments::{AddressDetails, BankDebitBilling, BankDebitData, PaymentMethodData}, +}; +use hex; +pub mod helpers; +pub mod transformers; + +use common_utils::{ + consts, + crypto::{HmacSha256, SignMessage}, + ext_traits::AsyncExt, + generate_id, +}; +use data_models::payments::PaymentIntent; +use error_stack::{IntoReport, ResultExt}; +#[cfg(feature = "kms")] +pub use external_services::kms; +use helpers::PaymentAuthConnectorDataExt; +use masking::{ExposeInterface, PeekInterface}; +use pm_auth::{ + connector::plaid::transformers::PlaidAuthType, + types::{ + self as pm_auth_types, + api::{ + auth_service::{BankAccountCredentials, ExchangeToken, LinkToken}, + BoxedConnectorIntegration, PaymentAuthConnectorData, + }, + }, +}; + +use crate::{ + core::{ + errors::{self, ApiErrorResponse, RouterResponse, RouterResult, StorageErrorExt}, + payment_methods::cards, + payments::helpers as oss_helpers, + pm_auth::helpers::{self as pm_auth_helpers}, + }, + db::StorageInterface, + logger, + routes::AppState, + services::{ + pm_auth::{self as pm_auth_services}, + ApplicationResponse, + }, + types::{ + self, + domain::{self, types::decrypt}, + storage, + transformers::ForeignTryFrom, + }, +}; + +pub async fn create_link_token( + state: AppState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + payload: api_models::pm_auth::LinkTokenCreateRequest, +) -> RouterResponse { + let db = &*state.store; + + let redis_conn = db + .get_redis_conn() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get redis connection")?; + + let pm_auth_key = format!("pm_auth_{}", payload.payment_id); + + let pm_auth_configs = redis_conn + .get_and_deserialize_key::>( + pm_auth_key.as_str(), + "Vec", + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get payment method auth choices from redis")?; + + let selected_config = pm_auth_configs + .into_iter() + .find(|config| { + config.payment_method == payload.payment_method + && config.payment_method_type == payload.payment_method_type + }) + .ok_or(ApiErrorResponse::GenericNotFoundError { + message: "payment method auth connector name not found".to_string(), + }) + .into_report()?; + + let connector_name = selected_config.connector_name.as_str(); + + let connector = PaymentAuthConnectorData::get_connector_by_name(connector_name)?; + let connector_integration: BoxedConnectorIntegration< + '_, + LinkToken, + pm_auth_types::LinkTokenRequest, + pm_auth_types::LinkTokenResponse, + > = connector.connector.get_connector_integration(); + + let payment_intent = oss_helpers::verify_payment_intent_time_and_client_secret( + &*state.store, + &merchant_account, + payload.client_secret, + ) + .await?; + + let billing_country = payment_intent + .as_ref() + .async_map(|pi| async { + oss_helpers::get_address_by_id( + &*state.store, + pi.billing_address_id.clone(), + &key_store, + pi.payment_id.clone(), + merchant_account.merchant_id.clone(), + merchant_account.storage_scheme, + ) + .await + }) + .await + .transpose()? + .flatten() + .and_then(|address| address.country) + .map(|country| country.to_string()); + + let merchant_connector_account = state + .store + .find_by_merchant_connector_account_merchant_id_merchant_connector_id( + merchant_account.merchant_id.as_str(), + &selected_config.mca_id, + &key_store, + ) + .await + .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: merchant_account.merchant_id.clone(), + })?; + + let auth_type = helpers::get_connector_auth_type(merchant_connector_account)?; + + let router_data = pm_auth_types::LinkTokenRouterData { + flow: std::marker::PhantomData, + merchant_id: Some(merchant_account.merchant_id), + connector: Some(connector_name.to_string()), + request: pm_auth_types::LinkTokenRequest { + client_name: "HyperSwitch".to_string(), + country_codes: Some(vec![billing_country.ok_or( + errors::ApiErrorResponse::MissingRequiredField { + field_name: "billing_country", + }, + )?]), + language: payload.language, + user_info: payment_intent.and_then(|pi| pi.customer_id), + }, + response: Ok(pm_auth_types::LinkTokenResponse { + link_token: "".to_string(), + }), + connector_http_status_code: None, + connector_auth_type: auth_type, + }; + + let connector_resp = pm_auth_services::execute_connector_processing_step( + state.as_ref(), + connector_integration, + &router_data, + &connector.connector_name, + ) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed while calling link token creation connector api")?; + + let link_token_resp = + connector_resp + .response + .map_err(|err| ApiErrorResponse::ExternalConnectorError { + code: err.code, + message: err.message, + connector: connector.connector_name.to_string(), + status_code: err.status_code, + reason: err.reason, + })?; + + let response = api_models::pm_auth::LinkTokenCreateResponse { + link_token: link_token_resp.link_token, + connector: connector.connector_name.to_string(), + }; + + Ok(ApplicationResponse::Json(response)) +} + +impl ForeignTryFrom<&types::ConnectorAuthType> for PlaidAuthType { + type Error = errors::ConnectorError; + + fn foreign_try_from(auth_type: &types::ConnectorAuthType) -> Result { + match auth_type { + types::ConnectorAuthType::BodyKey { api_key, key1 } => { + Ok::(Self { + client_id: api_key.to_owned(), + secret: key1.to_owned(), + }) + } + _ => Err(errors::ConnectorError::FailedToObtainAuthType), + } + } +} + +pub async fn exchange_token_core( + state: AppState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + payload: api_models::pm_auth::ExchangeTokenCreateRequest, +) -> RouterResponse<()> { + let db = &*state.store; + + let config = get_selected_config_from_redis(db, &payload).await?; + + let connector_name = config.connector_name.as_str(); + + let connector = + pm_auth_types::api::PaymentAuthConnectorData::get_connector_by_name(connector_name)?; + + let merchant_connector_account = state + .store + .find_by_merchant_connector_account_merchant_id_merchant_connector_id( + merchant_account.merchant_id.as_str(), + &config.mca_id, + &key_store, + ) + .await + .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: merchant_account.merchant_id.clone(), + })?; + + let auth_type = helpers::get_connector_auth_type(merchant_connector_account.clone())?; + + let access_token = get_access_token_from_exchange_api( + &connector, + connector_name, + &payload, + &auth_type, + &state, + ) + .await?; + + let bank_account_details_resp = get_bank_account_creds( + connector, + &merchant_account, + connector_name, + &access_token, + auth_type, + &state, + None, + ) + .await?; + + Box::pin(store_bank_details_in_payment_methods( + key_store, + payload, + merchant_account, + state, + bank_account_details_resp, + (connector_name, access_token), + merchant_connector_account.merchant_connector_id, + )) + .await?; + + Ok(ApplicationResponse::StatusOk) +} + +async fn store_bank_details_in_payment_methods( + key_store: domain::MerchantKeyStore, + payload: api_models::pm_auth::ExchangeTokenCreateRequest, + merchant_account: domain::MerchantAccount, + state: AppState, + bank_account_details_resp: pm_auth_types::BankAccountCredentialsResponse, + connector_details: (&str, String), + mca_id: String, +) -> RouterResult<()> { + let key = key_store.key.get_inner().peek(); + let db = &*state.clone().store; + let (connector_name, access_token) = connector_details; + + let payment_intent = db + .find_payment_intent_by_payment_id_merchant_id( + &payload.payment_id, + &merchant_account.merchant_id, + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(ApiErrorResponse::PaymentNotFound)?; + + let customer_id = payment_intent + .customer_id + .ok_or(ApiErrorResponse::CustomerNotFound)?; + + let payment_methods = db + .find_payment_method_by_customer_id_merchant_id_list( + &customer_id, + &merchant_account.merchant_id, + ) + .await + .change_context(ApiErrorResponse::InternalServerError)?; + + let mut hash_to_payment_method: HashMap< + String, + ( + storage::PaymentMethod, + payment_methods::PaymentMethodDataBankCreds, + ), + > = HashMap::new(); + + for pm in payment_methods { + if pm.payment_method == enums::PaymentMethod::BankDebit { + let bank_details_pm_data = decrypt::( + pm.payment_method_data.clone(), + key, + ) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("unable to decrypt bank account details")? + .map(|x| x.into_inner().expose()) + .map(|v| { + serde_json::from_value::(v) + .into_report() + .change_context(errors::StorageError::DeserializationFailed) + .attach_printable("Failed to deserialize Payment Method Auth config") + }) + .transpose() + .unwrap_or_else(|err| { + logger::error!(error=?err); + None + }) + .and_then(|pmd| match pmd { + payment_methods::PaymentMethodsData::BankDetails(bank_creds) => Some(bank_creds), + _ => None, + }) + .ok_or(ApiErrorResponse::InternalServerError)?; + + hash_to_payment_method.insert( + bank_details_pm_data.hash.clone(), + (pm, bank_details_pm_data), + ); + } + } + + #[cfg(feature = "kms")] + let pm_auth_key = kms::get_kms_client(&state.conf.kms) + .await + .decrypt(state.conf.payment_method_auth.pm_auth_key.clone()) + .await + .change_context(ApiErrorResponse::InternalServerError)?; + + #[cfg(not(feature = "kms"))] + let pm_auth_key = state.conf.payment_method_auth.pm_auth_key.clone(); + + let mut update_entries: Vec<(storage::PaymentMethod, storage::PaymentMethodUpdate)> = + Vec::new(); + let mut new_entries: Vec = Vec::new(); + + for creds in bank_account_details_resp.credentials { + let hash_string = format!("{}-{}", creds.account_number, creds.routing_number); + let generated_hash = hex::encode( + HmacSha256::sign_message(&HmacSha256, pm_auth_key.as_bytes(), hash_string.as_bytes()) + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to sign the message")?, + ); + + let contains_account = hash_to_payment_method.get(&generated_hash); + let mut pmd = payment_methods::PaymentMethodDataBankCreds { + mask: creds + .account_number + .chars() + .rev() + .take(4) + .collect::() + .chars() + .rev() + .collect::(), + hash: generated_hash, + account_type: creds.account_type, + account_name: creds.account_name, + payment_method_type: creds.payment_method_type, + connector_details: vec![payment_methods::BankAccountConnectorDetails { + connector: connector_name.to_string(), + mca_id: mca_id.clone(), + access_token: payment_methods::BankAccountAccessCreds::AccessToken( + access_token.clone(), + ), + account_id: creds.account_id, + }], + }; + + if let Some((pm, details)) = contains_account { + pmd.connector_details.extend( + details + .connector_details + .clone() + .into_iter() + .filter(|conn| conn.mca_id != mca_id), + ); + + let payment_method_data = payment_methods::PaymentMethodsData::BankDetails(pmd); + let encrypted_data = + cards::create_encrypted_payment_method_data(&key_store, Some(payment_method_data)) + .await + .ok_or(ApiErrorResponse::InternalServerError)?; + let pm_update = storage::PaymentMethodUpdate::PaymentMethodDataUpdate { + payment_method_data: Some(encrypted_data), + }; + + update_entries.push((pm.clone(), pm_update)); + } else { + let payment_method_data = payment_methods::PaymentMethodsData::BankDetails(pmd); + let encrypted_data = + cards::create_encrypted_payment_method_data(&key_store, Some(payment_method_data)) + .await + .ok_or(ApiErrorResponse::InternalServerError)?; + let pm_id = generate_id(consts::ID_LENGTH, "pm"); + let pm_new = storage::PaymentMethodNew { + customer_id: customer_id.clone(), + merchant_id: merchant_account.merchant_id.clone(), + payment_method_id: pm_id, + payment_method: enums::PaymentMethod::BankDebit, + payment_method_type: Some(creds.payment_method_type), + payment_method_issuer: None, + scheme: None, + metadata: None, + payment_method_data: Some(encrypted_data), + ..storage::PaymentMethodNew::default() + }; + + new_entries.push(pm_new); + }; + } + + store_in_db(update_entries, new_entries, db).await?; + + Ok(()) +} + +async fn store_in_db( + update_entries: Vec<(storage::PaymentMethod, storage::PaymentMethodUpdate)>, + new_entries: Vec, + db: &dyn StorageInterface, +) -> RouterResult<()> { + let update_entries_futures = update_entries + .into_iter() + .map(|(pm, pm_update)| db.update_payment_method(pm, pm_update)) + .collect::>(); + + let new_entries_futures = new_entries + .into_iter() + .map(|pm_new| db.insert_payment_method(pm_new)) + .collect::>(); + + let update_futures = futures::future::join_all(update_entries_futures); + let new_futures = futures::future::join_all(new_entries_futures); + + let (update, new) = tokio::join!(update_futures, new_futures); + + let _ = update + .into_iter() + .map(|res| res.map_err(|err| logger::error!("Payment method storage failed {err:?}"))); + + let _ = new + .into_iter() + .map(|res| res.map_err(|err| logger::error!("Payment method storage failed {err:?}"))); + + Ok(()) +} + +pub async fn get_bank_account_creds( + connector: PaymentAuthConnectorData, + merchant_account: &domain::MerchantAccount, + connector_name: &str, + access_token: &str, + auth_type: pm_auth_types::ConnectorAuthType, + state: &AppState, + bank_account_id: Option, +) -> RouterResult { + let connector_integration_bank_details: BoxedConnectorIntegration< + '_, + BankAccountCredentials, + pm_auth_types::BankAccountCredentialsRequest, + pm_auth_types::BankAccountCredentialsResponse, + > = connector.connector.get_connector_integration(); + + let router_data_bank_details = pm_auth_types::BankDetailsRouterData { + flow: std::marker::PhantomData, + merchant_id: Some(merchant_account.merchant_id.clone()), + connector: Some(connector_name.to_string()), + request: pm_auth_types::BankAccountCredentialsRequest { + access_token: access_token.to_string(), + optional_ids: bank_account_id + .map(|id| pm_auth_types::BankAccountOptionalIDs { ids: vec![id] }), + }, + response: Ok(pm_auth_types::BankAccountCredentialsResponse { + credentials: Vec::new(), + }), + connector_http_status_code: None, + connector_auth_type: auth_type, + }; + + let bank_details_resp = pm_auth_services::execute_connector_processing_step( + state, + connector_integration_bank_details, + &router_data_bank_details, + &connector.connector_name, + ) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed while calling bank account details connector api")?; + + let bank_account_details_resp = + bank_details_resp + .response + .map_err(|err| ApiErrorResponse::ExternalConnectorError { + code: err.code, + message: err.message, + connector: connector.connector_name.to_string(), + status_code: err.status_code, + reason: err.reason, + })?; + + Ok(bank_account_details_resp) +} + +async fn get_access_token_from_exchange_api( + connector: &PaymentAuthConnectorData, + connector_name: &str, + payload: &api_models::pm_auth::ExchangeTokenCreateRequest, + auth_type: &pm_auth_types::ConnectorAuthType, + state: &AppState, +) -> RouterResult { + let connector_integration: BoxedConnectorIntegration< + '_, + ExchangeToken, + pm_auth_types::ExchangeTokenRequest, + pm_auth_types::ExchangeTokenResponse, + > = connector.connector.get_connector_integration(); + + let router_data = pm_auth_types::ExchangeTokenRouterData { + flow: std::marker::PhantomData, + merchant_id: None, + connector: Some(connector_name.to_string()), + request: pm_auth_types::ExchangeTokenRequest { + public_token: payload.public_token.clone(), + }, + response: Ok(pm_auth_types::ExchangeTokenResponse { + access_token: "".to_string(), + }), + connector_http_status_code: None, + connector_auth_type: auth_type.clone(), + }; + + let resp = pm_auth_services::execute_connector_processing_step( + state, + connector_integration, + &router_data, + &connector.connector_name, + ) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed while calling exchange token connector api")?; + + let exchange_token_resp = + resp.response + .map_err(|err| ApiErrorResponse::ExternalConnectorError { + code: err.code, + message: err.message, + connector: connector.connector_name.to_string(), + status_code: err.status_code, + reason: err.reason, + })?; + + let access_token = exchange_token_resp.access_token; + Ok(access_token) +} + +async fn get_selected_config_from_redis( + db: &dyn StorageInterface, + payload: &api_models::pm_auth::ExchangeTokenCreateRequest, +) -> RouterResult { + let redis_conn = db + .get_redis_conn() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get redis connection")?; + + let pm_auth_key = format!("pm_auth_{}", payload.payment_id); + + let pm_auth_configs = redis_conn + .get_and_deserialize_key::>( + pm_auth_key.as_str(), + "Vec", + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get payment method auth choices from redis")?; + + let selected_config = pm_auth_configs + .iter() + .find(|conf| { + conf.payment_method == payload.payment_method + && conf.payment_method_type == payload.payment_method_type + }) + .ok_or(ApiErrorResponse::GenericNotFoundError { + message: "connector name not found".to_string(), + }) + .into_report()? + .clone(); + + Ok(selected_config) +} + +pub async fn retrieve_payment_method_from_auth_service( + state: &AppState, + key_store: &domain::MerchantKeyStore, + auth_token: &payment_methods::BankAccountConnectorDetails, + payment_intent: &PaymentIntent, +) -> RouterResult> { + let db = state.store.as_ref(); + + let connector = pm_auth_types::api::PaymentAuthConnectorData::get_connector_by_name( + auth_token.connector.as_str(), + )?; + + let merchant_account = db + .find_merchant_account_by_merchant_id(&payment_intent.merchant_id, key_store) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; + + let mca = db + .find_by_merchant_connector_account_merchant_id_merchant_connector_id( + &payment_intent.merchant_id, + &auth_token.mca_id, + key_store, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: auth_token.mca_id.clone(), + }) + .attach_printable( + "error while fetching merchant_connector_account from merchant_id and connector name", + )?; + + let auth_type = pm_auth_helpers::get_connector_auth_type(mca)?; + + let BankAccountAccessCreds::AccessToken(access_token) = &auth_token.access_token; + + let bank_account_creds = get_bank_account_creds( + connector, + &merchant_account, + &auth_token.connector, + access_token, + auth_type, + state, + Some(auth_token.account_id.clone()), + ) + .await?; + + logger::debug!("bank_creds: {:?}", bank_account_creds); + + let bank_account = bank_account_creds + .credentials + .first() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .into_report() + .attach_printable("Bank account details not found")?; + + let mut bank_type = None; + if let Some(account_type) = bank_account.account_type.clone() { + bank_type = api_models::enums::BankType::from_str(account_type.as_str()) + .map_err(|error| logger::error!(%error,"unable to parse account_type {account_type:?}")) + .ok(); + } + + let address = oss_helpers::get_address_by_id( + &*state.store, + payment_intent.billing_address_id.clone(), + key_store, + payment_intent.payment_id.clone(), + merchant_account.merchant_id.clone(), + merchant_account.storage_scheme, + ) + .await?; + + let name = address + .as_ref() + .and_then(|addr| addr.first_name.clone().map(|name| name.into_inner())); + + let address_details = address.clone().map(|addr| { + let line1 = addr.line1.map(|line1| line1.into_inner()); + let line2 = addr.line2.map(|line2| line2.into_inner()); + let line3 = addr.line3.map(|line3| line3.into_inner()); + let zip = addr.zip.map(|zip| zip.into_inner()); + let state = addr.state.map(|state| state.into_inner()); + let first_name = addr.first_name.map(|first_name| first_name.into_inner()); + let last_name = addr.last_name.map(|last_name| last_name.into_inner()); + + AddressDetails { + city: addr.city, + country: addr.country, + line1, + line2, + line3, + zip, + state, + first_name, + last_name, + } + }); + let payment_method_data = PaymentMethodData::BankDebit(BankDebitData::AchBankDebit { + billing_details: BankDebitBilling { + name: name.unwrap_or_default(), + email: common_utils::pii::Email::from(masking::Secret::new("".to_string())), + address: address_details, + }, + account_number: masking::Secret::new(bank_account.account_number.clone()), + routing_number: masking::Secret::new(bank_account.routing_number.clone()), + card_holder_name: None, + bank_account_holder_name: None, + bank_name: None, + bank_type, + bank_holder_type: None, + }); + + Ok(Some((payment_method_data, enums::PaymentMethod::BankDebit))) +} diff --git a/crates/router/src/core/pm_auth/helpers.rs b/crates/router/src/core/pm_auth/helpers.rs new file mode 100644 index 000000000000..43d30705a803 --- /dev/null +++ b/crates/router/src/core/pm_auth/helpers.rs @@ -0,0 +1,33 @@ +use common_utils::ext_traits::ValueExt; +use error_stack::{IntoReport, ResultExt}; +use pm_auth::types::{self as pm_auth_types, api::BoxedPaymentAuthConnector}; + +use crate::{ + core::errors::{self, ApiErrorResponse}, + types::{self, domain, transformers::ForeignTryFrom}, +}; + +pub trait PaymentAuthConnectorDataExt { + fn get_connector_by_name(name: &str) -> errors::CustomResult + where + Self: Sized; + fn convert_connector( + connector_name: pm_auth_types::PaymentMethodAuthConnectors, + ) -> errors::CustomResult; +} + +pub fn get_connector_auth_type( + merchant_connector_account: domain::MerchantConnectorAccount, +) -> errors::CustomResult { + let auth_type: types::ConnectorAuthType = merchant_connector_account + .connector_account_details + .parse_value("ConnectorAuthType") + .change_context(ApiErrorResponse::MerchantConnectorAccountNotFound { + id: "ConnectorAuthType".to_string(), + })?; + + pm_auth_types::ConnectorAuthType::foreign_try_from(auth_type) + .into_report() + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed while converting ConnectorAuthType") +} diff --git a/crates/router/src/core/pm_auth/transformers.rs b/crates/router/src/core/pm_auth/transformers.rs new file mode 100644 index 000000000000..8a1369c2e02f --- /dev/null +++ b/crates/router/src/core/pm_auth/transformers.rs @@ -0,0 +1,18 @@ +use pm_auth::types::{self as pm_auth_types}; + +use crate::{core::errors, types, types::transformers::ForeignTryFrom}; + +impl ForeignTryFrom for pm_auth_types::ConnectorAuthType { + type Error = errors::ConnectorError; + fn foreign_try_from(auth_type: types::ConnectorAuthType) -> Result { + match auth_type { + types::ConnectorAuthType::BodyKey { api_key, key1 } => { + Ok::(Self::BodyKey { + client_id: api_key.to_owned(), + secret: key1.to_owned(), + }) + } + _ => Err(errors::ConnectorError::FailedToObtainAuthType), + } + } +} diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 7d0d599cc4ed..e2c538ca80ee 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -1,10 +1,17 @@ use api_models::user as user_api; use diesel_models::{enums::UserStatus, user as storage_user}; -use error_stack::{IntoReport, ResultExt}; -use masking::{ExposeInterface, Secret}; +#[cfg(feature = "email")] +use error_stack::IntoReport; +use error_stack::ResultExt; +use masking::ExposeInterface; +#[cfg(feature = "email")] use router_env::env; +#[cfg(feature = "email")] +use router_env::logger; use super::errors::{UserErrors, UserResponse}; +#[cfg(feature = "email")] +use crate::services::email::types as email_types; use crate::{ consts, db::user::UserInterface, @@ -13,11 +20,114 @@ use crate::{ types::domain, utils, }; +pub mod dashboard_metadata; #[cfg(feature = "dummy_connector")] pub mod sample_data; -pub mod dashboard_metadata; +#[cfg(feature = "email")] +pub async fn signup_with_merchant_id( + state: AppState, + request: user_api::SignUpWithMerchantIdRequest, +) -> UserResponse { + let new_user = domain::NewUser::try_from(request.clone())?; + new_user + .get_new_merchant() + .get_new_organization() + .insert_org_in_db(state.clone()) + .await?; + + let user_from_db = new_user + .insert_user_and_merchant_in_db(state.clone()) + .await?; + + let user_role = new_user + .insert_user_role_in_db( + state.clone(), + consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(), + UserStatus::Active, + ) + .await?; + + let email_contents = email_types::ResetPassword { + recipient_email: user_from_db.get_email().try_into()?, + user_name: domain::UserName::new(user_from_db.get_name())?, + settings: state.conf.clone(), + subject: "Get back to Hyperswitch - Reset Your Password Now", + }; + + let send_email_result = state + .email_client + .compose_and_send_email( + Box::new(email_contents), + state.conf.proxy.https_url.as_ref(), + ) + .await; + + logger::info!(?send_email_result); + Ok(ApplicationResponse::Json(user_api::AuthorizeResponse { + is_email_sent: send_email_result.is_ok(), + user_id: user_from_db.get_user_id().to_string(), + merchant_id: user_role.merchant_id, + })) +} + +pub async fn signup( + state: AppState, + request: user_api::SignUpRequest, +) -> UserResponse { + let new_user = domain::NewUser::try_from(request)?; + new_user + .get_new_merchant() + .get_new_organization() + .insert_org_in_db(state.clone()) + .await?; + let user_from_db = new_user + .insert_user_and_merchant_in_db(state.clone()) + .await?; + let user_role = new_user + .insert_user_role_in_db( + state.clone(), + consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(), + UserStatus::Active, + ) + .await?; + let token = + utils::user::generate_jwt_auth_token(state.clone(), &user_from_db, &user_role).await?; + + Ok(ApplicationResponse::Json( + utils::user::get_dashboard_entry_response(state, user_from_db, user_role, token)?, + )) +} + +pub async fn signin( + state: AppState, + request: user_api::SignInRequest, +) -> UserResponse { + let user_from_db: domain::UserFromStorage = state + .store + .find_user_by_email(request.email.clone().expose().expose().as_str()) + .await + .map_err(|e| { + if e.current_context().is_db_not_found() { + e.change_context(UserErrors::InvalidCredentials) + } else { + e.change_context(UserErrors::InternalServerError) + } + })? + .into(); + + user_from_db.compare_password(request.password)?; + + let user_role = user_from_db.get_role_from_db(state.clone()).await?; + let token = + utils::user::generate_jwt_auth_token(state.clone(), &user_from_db, &user_role).await?; + + Ok(ApplicationResponse::Json( + utils::user::get_dashboard_entry_response(state, user_from_db, user_role, token)?, + )) +} +#[cfg(feature = "email")] pub async fn connect_account( state: AppState, request: user_api::ConnectAccountRequest, @@ -29,26 +139,34 @@ pub async fn connect_account( if let Ok(found_user) = find_user { let user_from_db: domain::UserFromStorage = found_user.into(); + let user_role = user_from_db.get_role_from_db(state.clone()).await?; - user_from_db.compare_password(request.password)?; + let email_contents = email_types::MagicLink { + recipient_email: domain::UserEmail::from_pii_email(user_from_db.get_email())?, + settings: state.conf.clone(), + user_name: domain::UserName::new(user_from_db.get_name())?, + subject: "Unlock Hyperswitch: Use Your Magic Link to Sign In", + }; + + let send_email_result = state + .email_client + .compose_and_send_email( + Box::new(email_contents), + state.conf.proxy.https_url.as_ref(), + ) + .await; - let user_role = user_from_db.get_role_from_db(state.clone()).await?; - let jwt_token = user_from_db - .get_jwt_auth_token(state.clone(), user_role.org_id) - .await?; + logger::info!(?send_email_result); return Ok(ApplicationResponse::Json( user_api::ConnectAccountResponse { - token: Secret::new(jwt_token), - merchant_id: user_role.merchant_id, - name: user_from_db.get_name(), - email: user_from_db.get_email(), - verification_days_left: None, - user_role: user_role.role_id, + is_email_sent: send_email_result.is_ok(), user_id: user_from_db.get_user_id().to_string(), + merchant_id: user_role.merchant_id, }, )); } else if find_user + .as_ref() .map_err(|e| e.current_context().is_db_not_found()) .err() .unwrap_or(false) @@ -73,46 +191,35 @@ pub async fn connect_account( UserStatus::Active, ) .await?; - let jwt_token = user_from_db - .get_jwt_auth_token(state.clone(), user_role.org_id) - .await?; - - #[cfg(feature = "email")] - { - use router_env::logger; - - use crate::services::email::types as email_types; - let email_contents = email_types::VerifyEmail { - recipient_email: domain::UserEmail::from_pii_email(user_from_db.get_email())?, - settings: state.conf.clone(), - subject: "Welcome to the Hyperswitch community!", - }; + let email_contents = email_types::VerifyEmail { + recipient_email: domain::UserEmail::from_pii_email(user_from_db.get_email())?, + settings: state.conf.clone(), + subject: "Welcome to the Hyperswitch community!", + }; + + let send_email_result = state + .email_client + .compose_and_send_email( + Box::new(email_contents), + state.conf.proxy.https_url.as_ref(), + ) + .await; - let send_email_result = state - .email_client - .compose_and_send_email( - Box::new(email_contents), - state.conf.proxy.https_url.as_ref(), - ) - .await; - - logger::info!(?send_email_result); - } + logger::info!(?send_email_result); return Ok(ApplicationResponse::Json( user_api::ConnectAccountResponse { - token: Secret::new(jwt_token), - merchant_id: user_role.merchant_id, - name: user_from_db.get_name(), - email: user_from_db.get_email(), - verification_days_left: None, - user_role: user_role.role_id, + is_email_sent: send_email_result.is_ok(), user_id: user_from_db.get_user_id().to_string(), + merchant_id: user_role.merchant_id, }, )); } else { - Err(UserErrors::InternalServerError.into()) + Err(find_user + .err() + .map(|e| e.change_context(UserErrors::InternalServerError)) + .unwrap_or(UserErrors::InternalServerError.into())) } } @@ -127,11 +234,16 @@ pub async fn change_password( .change_context(UserErrors::InternalServerError)? .into(); - user.compare_password(request.old_password) + user.compare_password(request.old_password.to_owned()) .change_context(UserErrors::InvalidOldPassword)?; + if request.old_password == request.new_password { + return Err(UserErrors::ChangePasswordError.into()); + } + let new_password = domain::UserPassword::new(request.new_password)?; + let new_password_hash = - crate::utils::user::password::generate_password_hash(request.new_password)?; + utils::user::password::generate_password_hash(new_password.get_secret())?; let _ = UserInterface::update_user_by_user_id( &*state.store, @@ -148,6 +260,183 @@ pub async fn change_password( Ok(ApplicationResponse::StatusOk) } +#[cfg(feature = "email")] +pub async fn forgot_password( + state: AppState, + request: user_api::ForgotPasswordRequest, +) -> UserResponse<()> { + let user_email = domain::UserEmail::from_pii_email(request.email)?; + + let user_from_db = state + .store + .find_user_by_email(user_email.get_secret().expose().as_str()) + .await + .map_err(|e| { + if e.current_context().is_db_not_found() { + e.change_context(UserErrors::UserNotFound) + } else { + e.change_context(UserErrors::InternalServerError) + } + }) + .map(domain::UserFromStorage::from)?; + + let email_contents = email_types::ResetPassword { + recipient_email: domain::UserEmail::from_pii_email(user_from_db.get_email())?, + settings: state.conf.clone(), + user_name: domain::UserName::new(user_from_db.get_name())?, + subject: "Get back to Hyperswitch - Reset Your Password Now", + }; + + state + .email_client + .compose_and_send_email( + Box::new(email_contents), + state.conf.proxy.https_url.as_ref(), + ) + .await + .map_err(|e| e.change_context(UserErrors::InternalServerError))?; + + Ok(ApplicationResponse::StatusOk) +} + +#[cfg(feature = "email")] +pub async fn reset_password( + state: AppState, + request: user_api::ResetPasswordRequest, +) -> UserResponse<()> { + let token = + auth::decode_jwt::(request.token.expose().as_str(), &state) + .await + .change_context(UserErrors::LinkInvalid)?; + + let password = domain::UserPassword::new(request.password)?; + + let hash_password = utils::user::password::generate_password_hash(password.get_secret())?; + + //TODO: Create Update by email query + let user_id = state + .store + .find_user_by_email(token.get_email()) + .await + .change_context(UserErrors::InternalServerError)? + .user_id; + + state + .store + .update_user_by_user_id( + user_id.as_str(), + storage_user::UserUpdate::AccountUpdate { + name: None, + password: Some(hash_password), + is_verified: Some(true), + }, + ) + .await + .change_context(UserErrors::InternalServerError)?; + + //TODO: Update User role status for invited user + + Ok(ApplicationResponse::StatusOk) +} + +#[cfg(feature = "email")] +pub async fn invite_user( + state: AppState, + request: user_api::InviteUserRequest, + user_from_token: auth::UserFromToken, +) -> UserResponse { + let inviter_user = state + .store + .find_user_by_id(user_from_token.user_id.as_str()) + .await + .change_context(UserErrors::InternalServerError)?; + + if inviter_user.email == request.email { + return Err(UserErrors::InvalidRoleOperation.into()) + .attach_printable("User Inviting themself"); + } + + utils::user_role::validate_role_id(request.role_id.as_str())?; + let invitee_email = domain::UserEmail::from_pii_email(request.email.clone())?; + + let invitee_user = state + .store + .find_user_by_email(invitee_email.clone().get_secret().expose().as_str()) + .await; + + if let Ok(invitee_user) = invitee_user { + let invitee_user_from_db = domain::UserFromStorage::from(invitee_user); + + let now = common_utils::date_time::now(); + use diesel_models::user_role::UserRoleNew; + state + .store + .insert_user_role(UserRoleNew { + user_id: invitee_user_from_db.get_user_id().to_owned(), + merchant_id: user_from_token.merchant_id, + role_id: request.role_id, + org_id: user_from_token.org_id, + status: UserStatus::Active, + created_by: user_from_token.user_id.clone(), + last_modified_by: user_from_token.user_id, + created_at: now, + last_modified_at: now, + }) + .await + .map_err(|e| { + if e.current_context().is_db_unique_violation() { + e.change_context(UserErrors::UserExists) + } else { + e.change_context(UserErrors::InternalServerError) + } + })?; + + Ok(ApplicationResponse::Json(user_api::InviteUserResponse { + is_email_sent: false, + })) + } else if invitee_user + .as_ref() + .map_err(|e| e.current_context().is_db_not_found()) + .err() + .unwrap_or(false) + { + let new_user = domain::NewUser::try_from((request.clone(), user_from_token))?; + + new_user + .insert_user_in_db(state.store.as_ref()) + .await + .change_context(UserErrors::InternalServerError)?; + new_user + .clone() + .insert_user_role_in_db(state.clone(), request.role_id, UserStatus::InvitationSent) + .await + .change_context(UserErrors::InternalServerError)?; + + let email_contents = email_types::InviteUser { + recipient_email: invitee_email, + user_name: domain::UserName::new(new_user.get_name())?, + settings: state.conf.clone(), + subject: "You have been invited to join Hyperswitch Community!", + }; + + let send_email_result = state + .email_client + .compose_and_send_email( + Box::new(email_contents), + state.conf.proxy.https_url.as_ref(), + ) + .await; + + logger::info!(?send_email_result); + + Ok(ApplicationResponse::Json(user_api::InviteUserResponse { + is_email_sent: send_email_result.is_ok(), + })) + } else { + Err(UserErrors::InternalServerError.into()) + } +} + pub async fn create_internal_user( state: AppState, request: user_api::CreateInternalUserRequest, @@ -215,7 +504,7 @@ pub async fn switch_merchant_id( state: AppState, request: user_api::SwitchMerchantIdRequest, user_from_token: auth::UserFromToken, -) -> UserResponse { +) -> UserResponse { if !utils::user_role::is_internal_role(&user_from_token.role_id) { let merchant_list = utils::user_role::get_merchant_ids_for_user(state.clone(), &user_from_token.user_id) @@ -252,7 +541,7 @@ pub async fn switch_merchant_id( } })?; - let org_id = state + let _org_id = state .store .find_merchant_account_by_merchant_id(request.merchant_id.as_str(), &key_store) .await @@ -272,23 +561,23 @@ pub async fn switch_merchant_id( .await .change_context(UserErrors::InternalServerError)?; - let token = Box::pin(user.get_jwt_auth_token_with_custom_merchant_id( - state.clone(), + let token = utils::user::generate_jwt_auth_token_with_custom_merchant_id( + state, + &user, + &user_role, request.merchant_id.clone(), - org_id, - )) - .await? - .into(); + ) + .await?; Ok(ApplicationResponse::Json( - user_api::ConnectAccountResponse { - merchant_id: request.merchant_id, + user_api::SwitchMerchantResponse { token, name: user.get_name(), email: user.get_email(), user_id: user.get_user_id().to_string(), verification_days_left: None, user_role: user_role.role_id, + merchant_id: user_role.merchant_id, }, )) } @@ -350,3 +639,34 @@ pub async fn get_users_for_merchant_account( Ok(ApplicationResponse::Json(user_api::GetUsersResponse(users))) } + +#[cfg(feature = "email")] +pub async fn verify_email( + state: AppState, + req: user_api::VerifyEmailRequest, +) -> UserResponse { + let token = auth::decode_jwt::(&req.token.clone().expose(), &state) + .await + .change_context(UserErrors::LinkInvalid)?; + + let user = state + .store + .find_user_by_email(token.get_email()) + .await + .change_context(UserErrors::InternalServerError)?; + + let user = state + .store + .update_user_by_user_id(user.user_id.as_str(), storage_user::UserUpdate::VerifyUser) + .await + .change_context(UserErrors::InternalServerError)?; + + let user_from_db: domain::UserFromStorage = user.into(); + let user_role = user_from_db.get_role_from_db(state.clone()).await?; + let token = + utils::user::generate_jwt_auth_token(state.clone(), &user_from_db, &user_role).await?; + + Ok(ApplicationResponse::Json( + utils::user::get_dashboard_entry_response(state, user_from_db, user_role, token)?, + )) +} diff --git a/crates/router/src/core/user/dashboard_metadata.rs b/crates/router/src/core/user/dashboard_metadata.rs index de385fb8ed65..b537aa3ec732 100644 --- a/crates/router/src/core/user/dashboard_metadata.rs +++ b/crates/router/src/core/user/dashboard_metadata.rs @@ -81,12 +81,17 @@ fn parse_set_request(data_enum: api::SetMetaDataRequest) -> UserResult { Ok(types::MetaData::IntegrationMethod(req)) } + api::SetMetaDataRequest::ConfigurationType(req) => { + Ok(types::MetaData::ConfigurationType(req)) + } api::SetMetaDataRequest::IntegrationCompleted => { Ok(types::MetaData::IntegrationCompleted(true)) } api::SetMetaDataRequest::SPRoutingConfigured(req) => { Ok(types::MetaData::SPRoutingConfigured(req)) } + api::SetMetaDataRequest::Feedback(req) => Ok(types::MetaData::Feedback(req)), + api::SetMetaDataRequest::ProdIntent(req) => Ok(types::MetaData::ProdIntent(req)), api::SetMetaDataRequest::SPTestPayment => Ok(types::MetaData::SPTestPayment(true)), api::SetMetaDataRequest::DownloadWoocom => Ok(types::MetaData::DownloadWoocom(true)), api::SetMetaDataRequest::ConfigureWoocom => Ok(types::MetaData::ConfigureWoocom(true)), @@ -110,10 +115,13 @@ fn parse_get_request(data_enum: api::GetMetaDataRequest) -> DBEnum { api::GetMetaDataRequest::ConfiguredRouting => DBEnum::ConfiguredRouting, api::GetMetaDataRequest::TestPayment => DBEnum::TestPayment, api::GetMetaDataRequest::IntegrationMethod => DBEnum::IntegrationMethod, + api::GetMetaDataRequest::ConfigurationType => DBEnum::ConfigurationType, api::GetMetaDataRequest::IntegrationCompleted => DBEnum::IntegrationCompleted, api::GetMetaDataRequest::StripeConnected => DBEnum::StripeConnected, api::GetMetaDataRequest::PaypalConnected => DBEnum::PaypalConnected, api::GetMetaDataRequest::SPRoutingConfigured => DBEnum::SpRoutingConfigured, + api::GetMetaDataRequest::Feedback => DBEnum::Feedback, + api::GetMetaDataRequest::ProdIntent => DBEnum::ProdIntent, api::GetMetaDataRequest::SPTestPayment => DBEnum::SpTestPayment, api::GetMetaDataRequest::DownloadWoocom => DBEnum::DownloadWoocom, api::GetMetaDataRequest::ConfigureWoocom => DBEnum::ConfigureWoocom, @@ -158,6 +166,10 @@ fn into_response( let resp = utils::deserialize_to_response(data)?; Ok(api::GetMetaDataResponse::IntegrationMethod(resp)) } + DBEnum::ConfigurationType => { + let resp = utils::deserialize_to_response(data)?; + Ok(api::GetMetaDataResponse::ConfigurationType(resp)) + } DBEnum::IntegrationCompleted => Ok(api::GetMetaDataResponse::IntegrationCompleted( data.is_some(), )), @@ -173,6 +185,14 @@ fn into_response( let resp = utils::deserialize_to_response(data)?; Ok(api::GetMetaDataResponse::SPRoutingConfigured(resp)) } + DBEnum::Feedback => { + let resp = utils::deserialize_to_response(data)?; + Ok(api::GetMetaDataResponse::Feedback(resp)) + } + DBEnum::ProdIntent => { + let resp = utils::deserialize_to_response(data)?; + Ok(api::GetMetaDataResponse::ProdIntent(resp)) + } DBEnum::SpTestPayment => Ok(api::GetMetaDataResponse::SPTestPayment(data.is_some())), DBEnum::DownloadWoocom => Ok(api::GetMetaDataResponse::DownloadWoocom(data.is_some())), DBEnum::ConfigureWoocom => Ok(api::GetMetaDataResponse::ConfigureWoocom(data.is_some())), @@ -282,15 +302,54 @@ async fn insert_metadata( .await } types::MetaData::IntegrationMethod(data) => { - utils::insert_merchant_scoped_metadata_to_db( + let mut metadata = utils::insert_merchant_scoped_metadata_to_db( state, - user.user_id, - user.merchant_id, - user.org_id, + user.user_id.clone(), + user.merchant_id.clone(), + user.org_id.clone(), metadata_key, - data, + data.clone(), ) - .await + .await; + + if utils::is_update_required(&metadata) { + metadata = utils::update_merchant_scoped_metadata( + state, + user.user_id, + user.merchant_id, + user.org_id, + metadata_key, + data, + ) + .await + .change_context(UserErrors::InternalServerError); + } + metadata + } + types::MetaData::ConfigurationType(data) => { + let mut metadata = utils::insert_merchant_scoped_metadata_to_db( + state, + user.user_id.clone(), + user.merchant_id.clone(), + user.org_id.clone(), + metadata_key, + data.clone(), + ) + .await; + + if utils::is_update_required(&metadata) { + metadata = utils::update_merchant_scoped_metadata( + state, + user.user_id, + user.merchant_id, + user.org_id, + metadata_key, + data, + ) + .await + .change_context(UserErrors::InternalServerError); + } + metadata } types::MetaData::IntegrationCompleted(data) => { utils::insert_merchant_scoped_metadata_to_db( @@ -336,6 +395,56 @@ async fn insert_metadata( ) .await } + types::MetaData::Feedback(data) => { + let mut metadata = utils::insert_user_scoped_metadata_to_db( + state, + user.user_id.clone(), + user.merchant_id.clone(), + user.org_id.clone(), + metadata_key, + data.clone(), + ) + .await; + + if utils::is_update_required(&metadata) { + metadata = utils::update_user_scoped_metadata( + state, + user.user_id, + user.merchant_id, + user.org_id, + metadata_key, + data, + ) + .await + .change_context(UserErrors::InternalServerError); + } + metadata + } + types::MetaData::ProdIntent(data) => { + let mut metadata = utils::insert_user_scoped_metadata_to_db( + state, + user.user_id.clone(), + user.merchant_id.clone(), + user.org_id.clone(), + metadata_key, + data.clone(), + ) + .await; + + if utils::is_update_required(&metadata) { + metadata = utils::update_user_scoped_metadata( + state, + user.user_id, + user.merchant_id, + user.org_id, + metadata_key, + data, + ) + .await + .change_context(UserErrors::InternalServerError); + } + metadata + } types::MetaData::SPTestPayment(data) => { utils::insert_merchant_scoped_metadata_to_db( state, @@ -400,7 +509,8 @@ async fn fetch_metadata( metadata_keys: Vec, ) -> UserResult> { let mut dashboard_metadata = Vec::with_capacity(metadata_keys.len()); - let (merchant_scoped_enums, _) = utils::separate_metadata_type_based_on_scope(metadata_keys); + let (merchant_scoped_enums, user_scoped_enums) = + utils::separate_metadata_type_based_on_scope(metadata_keys); if !merchant_scoped_enums.is_empty() { let mut res = utils::get_merchant_scoped_metadata_from_db( @@ -413,6 +523,18 @@ async fn fetch_metadata( dashboard_metadata.append(&mut res); } + if !user_scoped_enums.is_empty() { + let mut res = utils::get_user_scoped_metadata_from_db( + state, + user.user_id.to_owned(), + user.merchant_id.to_owned(), + user.org_id.to_owned(), + user_scoped_enums, + ) + .await?; + dashboard_metadata.append(&mut res); + } + Ok(dashboard_metadata) } diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 670c25c814ed..724a698ff700 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -1,19 +1,11 @@ use std::{marker::PhantomData, str::FromStr}; -use api_models::{ - enums::{DisputeStage, DisputeStatus}, - payment_methods::{SurchargeDetailsResponse, SurchargeMetadata}, -}; +use api_models::enums::{DisputeStage, DisputeStatus}; use common_enums::RequestIncrementalAuthorization; #[cfg(feature = "payouts")] use common_utils::{crypto::Encryptable, pii::Email}; -use common_utils::{ - errors::CustomResult, - ext_traits::{AsyncExt, Encode}, -}; +use common_utils::{errors::CustomResult, ext_traits::AsyncExt}; use error_stack::{report, IntoReport, ResultExt}; -use euclid::enums as euclid_enums; -use redis_interface::errors::RedisError; use router_env::{instrument, tracing}; use uuid::Uuid; @@ -1071,70 +1063,6 @@ pub fn get_flow_name() -> RouterResult { .to_string()) } -#[instrument(skip_all)] -pub async fn persist_individual_surcharge_details_in_redis( - state: &AppState, - merchant_account: &domain::MerchantAccount, - surcharge_metadata: &SurchargeMetadata, -) -> RouterResult<()> { - if !surcharge_metadata.is_empty_result() { - let redis_conn = state - .store - .get_redis_conn() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to get redis connection")?; - let redis_key = SurchargeMetadata::get_surcharge_metadata_redis_key( - &surcharge_metadata.payment_attempt_id, - ); - - let mut value_list = Vec::with_capacity(surcharge_metadata.get_surcharge_results_size()); - for (key, value) in surcharge_metadata - .get_individual_surcharge_key_value_pairs() - .into_iter() - { - value_list.push(( - key, - Encode::::encode_to_string_of_json(&value) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to encode to string of json")?, - )); - } - let intent_fulfillment_time = merchant_account - .intent_fulfillment_time - .unwrap_or(consts::DEFAULT_FULFILLMENT_TIME); - redis_conn - .set_hash_fields(&redis_key, value_list, Some(intent_fulfillment_time)) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to write to redis")?; - } - Ok(()) -} - -#[instrument(skip_all)] -pub async fn get_individual_surcharge_detail_from_redis( - state: &AppState, - payment_method: &euclid_enums::PaymentMethod, - payment_method_type: &euclid_enums::PaymentMethodType, - card_network: Option, - payment_attempt_id: &str, -) -> CustomResult { - let redis_conn = state - .store - .get_redis_conn() - .attach_printable("Failed to get redis connection")?; - let redis_key = SurchargeMetadata::get_surcharge_metadata_redis_key(payment_attempt_id); - let value_key = SurchargeMetadata::get_surcharge_details_redis_hashset_key( - payment_method, - payment_method_type, - card_network.as_ref(), - ); - - redis_conn - .get_hash_field_and_deserialize(&redis_key, &value_key, "SurchargeDetailsResponse") - .await -} - pub fn get_request_incremental_authorization_value( request_incremental_authorization: Option, capture_method: Option, diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs index 6558cc6ace50..0cd4cb218810 100644 --- a/crates/router/src/db.rs +++ b/crates/router/src/db.rs @@ -1,5 +1,6 @@ pub mod address; pub mod api_keys; +pub mod authorization; pub mod business_profile; pub mod cache; pub mod capture; @@ -100,6 +101,7 @@ pub trait StorageInterface: + gsm::GsmInterface + user::UserInterface + user_role::UserRoleInterface + + authorization::AuthorizationInterface + user::sample_data::BatchSampleDataInterface + 'static { diff --git a/crates/router/src/db/authorization.rs b/crates/router/src/db/authorization.rs new file mode 100644 index 000000000000..f24daaf718ad --- /dev/null +++ b/crates/router/src/db/authorization.rs @@ -0,0 +1,104 @@ +use error_stack::IntoReport; + +use super::{MockDb, Store}; +use crate::{ + connection, + core::errors::{self, CustomResult}, + types::storage, +}; + +#[async_trait::async_trait] +pub trait AuthorizationInterface { + async fn insert_authorization( + &self, + authorization: storage::AuthorizationNew, + ) -> CustomResult; + + async fn find_all_authorizations_by_merchant_id_payment_id( + &self, + merchant_id: &str, + payment_id: &str, + ) -> CustomResult, errors::StorageError>; + + async fn update_authorization_by_merchant_id_authorization_id( + &self, + merchant_id: String, + authorization_id: String, + authorization: storage::AuthorizationUpdate, + ) -> CustomResult; +} + +#[async_trait::async_trait] +impl AuthorizationInterface for Store { + async fn insert_authorization( + &self, + authorization: storage::AuthorizationNew, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + authorization + .insert(&conn) + .await + .map_err(Into::into) + .into_report() + } + + async fn find_all_authorizations_by_merchant_id_payment_id( + &self, + merchant_id: &str, + payment_id: &str, + ) -> CustomResult, errors::StorageError> { + let conn = connection::pg_connection_read(self).await?; + storage::Authorization::find_by_merchant_id_payment_id(&conn, merchant_id, payment_id) + .await + .map_err(Into::into) + .into_report() + } + + async fn update_authorization_by_merchant_id_authorization_id( + &self, + merchant_id: String, + authorization_id: String, + authorization: storage::AuthorizationUpdate, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + storage::Authorization::update_by_merchant_id_authorization_id( + &conn, + merchant_id, + authorization_id, + authorization, + ) + .await + .map_err(Into::into) + .into_report() + } +} + +#[async_trait::async_trait] +impl AuthorizationInterface for MockDb { + async fn insert_authorization( + &self, + _authorization: storage::AuthorizationNew, + ) -> CustomResult { + // TODO: Implement function for `MockDb` + Err(errors::StorageError::MockDbError)? + } + + async fn find_all_authorizations_by_merchant_id_payment_id( + &self, + _merchant_id: &str, + _payment_id: &str, + ) -> CustomResult, errors::StorageError> { + // TODO: Implement function for `MockDb` + Err(errors::StorageError::MockDbError)? + } + + async fn update_authorization_by_merchant_id_authorization_id( + &self, + _merchant_id: String, + _authorization_id: String, + _authorization: storage::AuthorizationUpdate, + ) -> CustomResult { + // TODO: Implement function for `MockDb` + Err(errors::StorageError::MockDbError)? + } +} diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 32548e36b6fb..db94c1bcbca9 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -32,6 +32,7 @@ use crate::{ db::{ address::AddressInterface, api_keys::ApiKeyInterface, + authorization::AuthorizationInterface, business_profile::BusinessProfileInterface, capture::CaptureInterface, cards_info::CardsInfoInterface, @@ -2095,3 +2096,38 @@ impl BatchSampleDataInterface for KafkaStore { Ok(refunds_list) } } + +#[async_trait::async_trait] +impl AuthorizationInterface for KafkaStore { + async fn insert_authorization( + &self, + authorization: storage::AuthorizationNew, + ) -> CustomResult { + self.diesel_store.insert_authorization(authorization).await + } + + async fn find_all_authorizations_by_merchant_id_payment_id( + &self, + merchant_id: &str, + payment_id: &str, + ) -> CustomResult, errors::StorageError> { + self.diesel_store + .find_all_authorizations_by_merchant_id_payment_id(merchant_id, payment_id) + .await + } + + async fn update_authorization_by_merchant_id_authorization_id( + &self, + merchant_id: String, + authorization_id: String, + authorization: storage::AuthorizationUpdate, + ) -> CustomResult { + self.diesel_store + .update_authorization_by_merchant_id_authorization_id( + merchant_id, + authorization_id, + authorization, + ) + .await + } +} diff --git a/crates/router/src/db/refund.rs b/crates/router/src/db/refund.rs index 8ac8bd106eff..accb5e8f3f94 100644 --- a/crates/router/src/db/refund.rs +++ b/crates/router/src/db/refund.rs @@ -267,7 +267,7 @@ mod storage { #[cfg(feature = "kv_store")] mod storage { - use common_utils::date_time; + use common_utils::{date_time, fallback_reverse_lookup_not_found}; use error_stack::{IntoReport, ResultExt}; use redis_interface::HsetnxReply; use storage_impl::redis::kv_store::{kv_wrapper, KvOperation}; @@ -275,9 +275,8 @@ mod storage { use super::RefundInterface; use crate::{ connection, - core::errors::{self, CustomResult}, + core::errors::{self, utils::RedisErrorExt, CustomResult}, db::reverse_lookup::ReverseLookupInterface, - logger, services::Store, types::storage::{self as storage_types, enums, kv}, utils::{self, db_utils}, @@ -304,10 +303,12 @@ mod storage { match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => database_call().await, enums::MerchantStorageScheme::RedisKv => { - let lookup_id = format!("{merchant_id}_{internal_reference_id}"); - let lookup = self - .get_lookup_by_lookup_id(&lookup_id, storage_scheme) - .await?; + let lookup_id = format!("ref_inter_ref_{merchant_id}_{internal_reference_id}"); + let lookup = fallback_reverse_lookup_not_found!( + self.get_lookup_by_lookup_id(&lookup_id, storage_scheme) + .await, + database_call().await + ); let key = &lookup.pk_id; Box::pin(db_utils::try_redis_get_else_try_database_get( @@ -382,6 +383,50 @@ mod storage { }, }; + let mut reverse_lookups = vec![ + storage_types::ReverseLookupNew { + sk_id: field.clone(), + lookup_id: format!( + "ref_ref_id_{}_{}", + created_refund.merchant_id, created_refund.refund_id + ), + pk_id: key.clone(), + source: "refund".to_string(), + updated_by: storage_scheme.to_string(), + }, + // [#492]: A discussion is required on whether this is required? + storage_types::ReverseLookupNew { + sk_id: field.clone(), + lookup_id: format!( + "ref_inter_ref_{}_{}", + created_refund.merchant_id, created_refund.internal_reference_id + ), + pk_id: key.clone(), + source: "refund".to_string(), + updated_by: storage_scheme.to_string(), + }, + ]; + if let Some(connector_refund_id) = created_refund.to_owned().connector_refund_id + { + reverse_lookups.push(storage_types::ReverseLookupNew { + sk_id: field.clone(), + lookup_id: format!( + "ref_connector_{}_{}_{}", + created_refund.merchant_id, + connector_refund_id, + created_refund.connector + ), + pk_id: key.clone(), + source: "refund".to_string(), + updated_by: storage_scheme.to_string(), + }) + }; + let rev_look = reverse_lookups + .into_iter() + .map(|rev| self.insert_reverse_lookup(rev, storage_scheme)); + + futures::future::try_join_all(rev_look).await?; + match kv_wrapper::( self, KvOperation::::HSetNx( @@ -392,7 +437,7 @@ mod storage { &key, ) .await - .change_context(errors::StorageError::KVError)? + .map_err(|err| err.to_redis_failed_response(&key))? .try_into_hsetnx() { Ok(HsetnxReply::KeyNotSet) => Err(errors::StorageError::DuplicateValue { @@ -400,55 +445,7 @@ mod storage { key: Some(created_refund.refund_id), }) .into_report(), - Ok(HsetnxReply::KeySet) => { - let mut reverse_lookups = vec![ - storage_types::ReverseLookupNew { - sk_id: field.clone(), - lookup_id: format!( - "{}_{}", - created_refund.merchant_id, created_refund.refund_id - ), - pk_id: key.clone(), - source: "refund".to_string(), - updated_by: storage_scheme.to_string(), - }, - // [#492]: A discussion is required on whether this is required? - storage_types::ReverseLookupNew { - sk_id: field.clone(), - lookup_id: format!( - "{}_{}", - created_refund.merchant_id, - created_refund.internal_reference_id - ), - pk_id: key.clone(), - source: "refund".to_string(), - updated_by: storage_scheme.to_string(), - }, - ]; - if let Some(connector_refund_id) = - created_refund.to_owned().connector_refund_id - { - reverse_lookups.push(storage_types::ReverseLookupNew { - sk_id: field.clone(), - lookup_id: format!( - "{}_{}_{}", - created_refund.merchant_id, - connector_refund_id, - created_refund.connector - ), - pk_id: key, - source: "refund".to_string(), - updated_by: storage_scheme.to_string(), - }) - }; - let rev_look = reverse_lookups - .into_iter() - .map(|rev| self.insert_reverse_lookup(rev, storage_scheme)); - - futures::future::try_join_all(rev_look).await?; - - Ok(created_refund) - } + Ok(HsetnxReply::KeySet) => Ok(created_refund), Err(er) => Err(er).change_context(errors::StorageError::KVError), } } @@ -475,17 +472,14 @@ mod storage { match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => database_call().await, enums::MerchantStorageScheme::RedisKv => { - let lookup_id = format!("{merchant_id}_{connector_transaction_id}"); - let lookup = match self - .get_lookup_by_lookup_id(&lookup_id, storage_scheme) - .await - { - Ok(l) => l, - Err(err) => { - logger::error!(?err); - return Ok(vec![]); - } - }; + let lookup_id = + format!("pa_conn_trans_{merchant_id}_{connector_transaction_id}"); + let lookup = fallback_reverse_lookup_not_found!( + self.get_lookup_by_lookup_id(&lookup_id, storage_scheme) + .await, + database_call().await + ); + let key = &lookup.pk_id; let pattern = db_utils::generate_hscan_pattern_for_refund(&lookup.sk_id); @@ -550,7 +544,7 @@ mod storage { &key, ) .await - .change_context(errors::StorageError::KVError)? + .map_err(|err| err.to_redis_failed_response(&key))? .try_into_hset() .change_context(errors::StorageError::KVError)?; @@ -575,10 +569,12 @@ mod storage { match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => database_call().await, enums::MerchantStorageScheme::RedisKv => { - let lookup_id = format!("{merchant_id}_{refund_id}"); - let lookup = self - .get_lookup_by_lookup_id(&lookup_id, storage_scheme) - .await?; + let lookup_id = format!("ref_ref_id_{merchant_id}_{refund_id}"); + let lookup = fallback_reverse_lookup_not_found!( + self.get_lookup_by_lookup_id(&lookup_id, storage_scheme) + .await, + database_call().await + ); let key = &lookup.pk_id; Box::pin(db_utils::try_redis_get_else_try_database_get( @@ -620,10 +616,13 @@ mod storage { match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => database_call().await, enums::MerchantStorageScheme::RedisKv => { - let lookup_id = format!("{merchant_id}_{connector_refund_id}_{connector}"); - let lookup = self - .get_lookup_by_lookup_id(&lookup_id, storage_scheme) - .await?; + let lookup_id = + format!("ref_connector_{merchant_id}_{connector_refund_id}_{connector}"); + let lookup = fallback_reverse_lookup_not_found!( + self.get_lookup_by_lookup_id(&lookup_id, storage_scheme) + .await, + database_call().await + ); let key = &lookup.pk_id; Box::pin(db_utils::try_redis_get_else_try_database_get( @@ -998,7 +997,7 @@ impl RefundInterface for MockDb { let mut refund_meta_data = api_models::refunds::RefundListMetaData { connector: vec![], currency: vec![], - status: vec![], + refund_status: vec![], }; let mut unique_connectors = HashSet::new(); @@ -1017,7 +1016,7 @@ impl RefundInterface for MockDb { refund_meta_data.connector = unique_connectors.into_iter().collect(); refund_meta_data.currency = unique_currencies.into_iter().collect(); - refund_meta_data.status = unique_statuses.into_iter().collect(); + refund_meta_data.refund_status = unique_statuses.into_iter().collect(); Ok(refund_meta_data) } diff --git a/crates/router/src/db/reverse_lookup.rs b/crates/router/src/db/reverse_lookup.rs index 445e171fa277..344077f3ec0f 100644 --- a/crates/router/src/db/reverse_lookup.rs +++ b/crates/router/src/db/reverse_lookup.rs @@ -69,6 +69,7 @@ mod storage { use super::{ReverseLookupInterface, Store}; use crate::{ connection, + core::errors::utils::RedisErrorExt, errors::{self, CustomResult}, types::storage::{ enums, kv, @@ -109,7 +110,7 @@ mod storage { format!("reverse_lookup_{}", &created_rev_lookup.lookup_id), ) .await - .change_context(errors::StorageError::KVError)? + .map_err(|err| err.to_redis_failed_response(&created_rev_lookup.lookup_id))? .try_into_setnx() { Ok(SetnxReply::KeySet) => Ok(created_rev_lookup), diff --git a/crates/router/src/db/user/sample_data.rs b/crates/router/src/db/user/sample_data.rs index 11def9026854..ae98332cfc49 100644 --- a/crates/router/src/db/user/sample_data.rs +++ b/crates/router/src/db/user/sample_data.rs @@ -193,13 +193,7 @@ fn diesel_error_to_data_error(diesel_error: Report) -> Report { - StorageError::DatabaseError("No fields to update".to_string()) - } - DatabaseError::QueryGenerationFailed => { - StorageError::DatabaseError("Query generation failed".to_string()) - } - DatabaseError::Others => StorageError::DatabaseError("Others".to_string()), + err => StorageError::DatabaseError(error_stack::report!(*err)), }; diesel_error.change_context(new_err) } diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 035314f71dfb..3b4c7ce9b7d3 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -35,6 +35,7 @@ use storage_impl::errors::ApplicationResult; use tokio::sync::{mpsc, oneshot}; pub use self::env::logger; +pub(crate) use self::macros::*; use crate::{configs::settings, core::errors}; #[cfg(feature = "mimalloc")] @@ -146,6 +147,7 @@ pub fn mk_app( .service(routes::Gsm::server(state.clone())) .service(routes::PaymentLink::server(state.clone())) .service(routes::User::server(state.clone())) + .service(routes::ConnectorOnboarding::server(state.clone())) } #[cfg(all(feature = "olap", feature = "kms"))] diff --git a/crates/router/src/macros.rs b/crates/router/src/macros.rs index 33ed43fcc7ab..e6c9dba7d6e2 100644 --- a/crates/router/src/macros.rs +++ b/crates/router/src/macros.rs @@ -1,68 +1,4 @@ -#[macro_export] -macro_rules! newtype_impl { - ($is_pub:vis, $name:ident, $ty_path:path) => { - impl std::ops::Deref for $name { - type Target = $ty_path; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - impl std::ops::DerefMut for $name { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } - } - - impl From<$ty_path> for $name { - fn from(ty: $ty_path) -> Self { - Self(ty) - } - } - - impl $name { - pub fn into_inner(self) -> $ty_path { - self.0 - } - } - }; -} - -#[macro_export] -macro_rules! newtype { - ($is_pub:vis $name:ident = $ty_path:path) => { - $is_pub struct $name(pub $ty_path); - - $crate::newtype_impl!($is_pub, $name, $ty_path); - }; - - ($is_pub:vis $name:ident = $ty_path:path, derives = ($($trt:path),*)) => { - #[derive($($trt),*)] - $is_pub struct $name(pub $ty_path); - - $crate::newtype_impl!($is_pub, $name, $ty_path); - }; -} - -#[macro_export] -macro_rules! async_spawn { - ($t:block) => { - tokio::spawn(async move { $t }); - }; -} - -#[macro_export] -macro_rules! collect_missing_value_keys { - [$(($key:literal, $option:expr)),+] => { - { - let mut keys: Vec<&'static str> = Vec::new(); - $( - if $option.is_none() { - keys.push($key); - } - )* - keys - } - }; -} +pub use common_utils::{ + async_spawn, collect_missing_value_keys, fallback_reverse_lookup_not_found, newtype, + newtype_impl, +}; diff --git a/crates/router/src/openapi.rs b/crates/router/src/openapi.rs index cfb0268a9f80..95c36719cad1 100644 --- a/crates/router/src/openapi.rs +++ b/crates/router/src/openapi.rs @@ -175,6 +175,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::CaptureStatus, api_models::enums::ReconStatus, api_models::enums::ConnectorStatus, + api_models::enums::AuthorizationStatus, api_models::admin::MerchantConnectorCreate, api_models::admin::MerchantConnectorUpdate, api_models::admin::PrimaryBusinessDetails, @@ -315,8 +316,13 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::RequestSurchargeDetails, api_models::payments::PaymentAttemptResponse, api_models::payments::CaptureResponse, + api_models::payments::IncrementalAuthorizationResponse, api_models::payment_methods::RequiredFieldInfo, api_models::payment_methods::MaskedBankDetails, + api_models::payment_methods::SurchargeDetailsResponse, + api_models::payment_methods::SurchargeResponse, + api_models::payment_methods::SurchargePercentage, + api_models::payment_methods::RequestPaymentMethodTypes, api_models::refunds::RefundListRequest, api_models::refunds::RefundListResponse, api_models::payments::TimeRange, @@ -361,7 +367,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::PaymentLinkResponse, api_models::payments::RetrievePaymentLinkResponse, api_models::payments::PaymentLinkInitiateRequest, - api_models::payments::PaymentLinkObject + api_models::payments::PaymentLinkObject, )), modifiers(&SecurityAddon) )] diff --git a/crates/router/src/routes.rs b/crates/router/src/routes.rs index b19ef5d7016b..ec718b2dde9f 100644 --- a/crates/router/src/routes.rs +++ b/crates/router/src/routes.rs @@ -4,6 +4,8 @@ pub mod app; pub mod cache; pub mod cards_info; pub mod configs; +#[cfg(feature = "olap")] +pub mod connector_onboarding; #[cfg(any(feature = "olap", feature = "oltp"))] pub mod currency; pub mod customers; @@ -12,6 +14,8 @@ pub mod disputes; pub mod dummy_connector; pub mod ephemeral_key; pub mod files; +#[cfg(feature = "frm")] +pub mod fraud_check; pub mod gsm; pub mod health; pub mod lock_utils; @@ -36,6 +40,8 @@ pub mod verify_connector; pub mod webhooks; pub mod locker_migration; +#[cfg(any(feature = "olap", feature = "oltp"))] +pub mod pm_auth; #[cfg(feature = "dummy_connector")] pub use self::app::DummyConnector; #[cfg(any(feature = "olap", feature = "oltp"))] @@ -47,9 +53,9 @@ pub use self::app::Routing; #[cfg(all(feature = "olap", feature = "kms"))] pub use self::app::Verify; pub use self::app::{ - ApiKeys, AppState, BusinessProfile, Cache, Cards, Configs, Customers, Disputes, EphemeralKey, - Files, Gsm, Health, LockerMigrate, Mandates, MerchantAccount, MerchantConnectorAccount, - PaymentLink, PaymentMethods, Payments, Refunds, User, Webhooks, + ApiKeys, AppState, BusinessProfile, Cache, Cards, Configs, ConnectorOnboarding, Customers, + Disputes, EphemeralKey, Files, Gsm, Health, LockerMigrate, Mandates, MerchantAccount, + MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments, Refunds, User, Webhooks, }; #[cfg(feature = "stripe")] pub use super::compatibility::stripe::StripeApis; diff --git a/crates/router/src/routes/admin.rs b/crates/router/src/routes/admin.rs index ce6a2a97e28d..ab404125a384 100644 --- a/crates/router/src/routes/admin.rs +++ b/crates/router/src/routes/admin.rs @@ -360,7 +360,7 @@ pub async fn payment_connector_update( let flow = Flow::MerchantConnectorsUpdate; let (merchant_id, merchant_connector_id) = path.into_inner(); - api::server_wrap( + Box::pin(api::server_wrap( flow, state, &req, @@ -375,7 +375,7 @@ pub async fn payment_connector_update( req.headers(), ), api_locking::LockAction::NotApplicable, - ) + )) .await } /// Merchant Connector - Delete diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 8ba1c3b02c14..756f2777b26b 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -20,20 +20,24 @@ use super::currency; use super::dummy_connector::*; #[cfg(feature = "payouts")] use super::payouts::*; +#[cfg(feature = "oltp")] +use super::pm_auth; #[cfg(feature = "olap")] use super::routing as cloud_routing; #[cfg(all(feature = "olap", feature = "kms"))] use super::verification::{apple_pay_merchant_registration, retrieve_apple_pay_verified_domains}; #[cfg(feature = "olap")] use super::{ - admin::*, api_keys::*, disputes::*, files::*, gsm::*, locker_migration, payment_link::*, - user::*, user_role::*, + admin::*, api_keys::*, connector_onboarding::*, disputes::*, files::*, gsm::*, + locker_migration, payment_link::*, user::*, user_role::*, }; use super::{cache::*, health::*}; #[cfg(any(feature = "olap", feature = "oltp"))] use super::{configs::*, customers::*, mandates::*, payments::*, refunds::*}; #[cfg(feature = "oltp")] use super::{ephemeral_key::*, payment_methods::*, webhooks::*}; +#[cfg(all(feature = "frm", feature = "oltp"))] +use crate::routes::fraud_check as frm_routes; #[cfg(feature = "olap")] use crate::routes::verify_connector::payment_connector_verify; pub use crate::{ @@ -187,6 +191,16 @@ impl AppState { } }; + #[cfg(all(feature = "kms", feature = "olap"))] + #[allow(clippy::expect_used)] + { + conf.connector_onboarding = conf + .connector_onboarding + .decrypt_inner(kms_client) + .await + .expect("Failed to decrypt connector onboarding credentials"); + } + #[cfg(feature = "olap")] let pool = crate::analytics::AnalyticsProvider::from_conf(&conf.analytics).await; @@ -327,6 +341,14 @@ impl Payments { .service( web::resource("/{payment_id}/capture").route(web::post().to(payments_capture)), ) + .service( + web::resource("/{payment_id}/approve") + .route(web::post().to(payments_approve)), + ) + .service( + web::resource("/{payment_id}/reject") + .route(web::post().to(payments_reject)), + ) .service( web::resource("/redirect/{payment_id}/{merchant_id}/{attempt_id}") .route(web::get().to(payments_start)), @@ -346,6 +368,9 @@ impl Payments { web::resource("/{payment_id}/{merchant_id}/redirect/complete/{connector}") .route(web::get().to(payments_complete_authorize)) .route(web::post().to(payments_complete_authorize)), + ) + .service( + web::resource("/{payment_id}/incremental_authorization").route(web::post().to(payments_incremental_authorization)), ); } route @@ -535,6 +560,8 @@ impl PaymentMethods { .route(web::post().to(payment_method_update_api)) .route(web::delete().to(payment_method_delete_api)), ) + .service(web::resource("/auth/link").route(web::post().to(pm_auth::link_token_create))) + .service(web::resource("/auth/exchange").route(web::post().to(pm_auth::exchange_token))) } } @@ -640,7 +667,8 @@ impl Webhooks { pub fn server(config: AppState) -> Scope { use api_models::webhooks as webhook_type; - web::scope("/webhooks") + #[allow(unused_mut)] + let mut route = web::scope("/webhooks") .app_data(web::Data::new(config)) .service( web::resource("/{merchant_id}/{connector_id_or_name}") @@ -651,7 +679,17 @@ impl Webhooks { .route( web::put().to(receive_incoming_webhook::), ), - ) + ); + + #[cfg(feature = "frm")] + { + route = route.service( + web::resource("/frm_fulfillment") + .route(web::post().to(frm_routes::frm_fulfillment)), + ); + } + + route } } @@ -826,16 +864,8 @@ impl User { let mut route = web::scope("/user").app_data(web::Data::new(state)); route = route - .service(web::resource("/signin").route(web::post().to(user_connect_account))) - .service(web::resource("/signup").route(web::post().to(user_connect_account))) - .service(web::resource("/v2/signin").route(web::post().to(user_connect_account))) - .service(web::resource("/v2/signup").route(web::post().to(user_connect_account))) + .service(web::resource("/signin").route(web::post().to(user_signin))) .service(web::resource("/change_password").route(web::post().to(change_password))) - .service( - web::resource("/data/merchant") - .route(web::post().to(set_merchant_scoped_dashboard_metadata)), - ) - .service(web::resource("/data").route(web::get().to(get_multiple_dashboard_metadata))) .service(web::resource("/internal_signup").route(web::post().to(internal_user_signup))) .service(web::resource("/switch_merchant").route(web::post().to(switch_merchant_id))) .service( @@ -844,11 +874,15 @@ impl User { ) .service(web::resource("/switch/list").route(web::get().to(list_merchant_ids_for_user))) .service(web::resource("/user/list").route(web::get().to(get_user_details))) - // User Role APIs .service(web::resource("/permission_info").route(web::get().to(get_authorization_info))) .service(web::resource("/user/update_role").route(web::post().to(update_user_role))) .service(web::resource("/role/list").route(web::get().to(list_roles))) - .service(web::resource("/role/{role_id}").route(web::get().to(get_role))); + .service(web::resource("/role/{role_id}").route(web::get().to(get_role))) + .service( + web::resource("/data") + .route(web::get().to(get_multiple_dashboard_metadata)) + .route(web::post().to(set_dashboard_metadata)), + ); #[cfg(feature = "dummy_connector")] { @@ -858,6 +892,25 @@ impl User { .route(web::delete().to(delete_sample_data)), ) } + #[cfg(feature = "email")] + { + route = route + .service( + web::resource("/connect_account").route(web::post().to(user_connect_account)), + ) + .service(web::resource("/forgot_password").route(web::post().to(forgot_password))) + .service(web::resource("/reset_password").route(web::post().to(reset_password))) + .service(web::resource("user/invite").route(web::post().to(invite_user))) + .service( + web::resource("/signup_with_merchant_id") + .route(web::post().to(user_signup_with_merchant_id)), + ) + .service(web::resource("/verify_email").route(web::post().to(verify_email))) + } + #[cfg(not(feature = "email"))] + { + route = route.service(web::resource("/signup").route(web::post().to(user_signup))) + } route } } @@ -874,3 +927,15 @@ impl LockerMigrate { ) } } + +pub struct ConnectorOnboarding; + +#[cfg(feature = "olap")] +impl ConnectorOnboarding { + pub fn server(state: AppState) -> Scope { + web::scope("/connector_onboarding") + .app_data(web::Data::new(state)) + .service(web::resource("/action_url").route(web::post().to(get_action_url))) + .service(web::resource("/sync").route(web::post().to(sync_onboarding_status))) + } +} diff --git a/crates/router/src/routes/connector_onboarding.rs b/crates/router/src/routes/connector_onboarding.rs new file mode 100644 index 000000000000..b7c39b3c1d2e --- /dev/null +++ b/crates/router/src/routes/connector_onboarding.rs @@ -0,0 +1,47 @@ +use actix_web::{web, HttpRequest, HttpResponse}; +use api_models::connector_onboarding as api_types; +use router_env::Flow; + +use super::AppState; +use crate::{ + core::{api_locking, connector_onboarding as core}, + services::{api, authentication as auth, authorization::permissions::Permission}, +}; + +pub async fn get_action_url( + state: web::Data, + http_req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::GetActionUrl; + let req_payload = json_payload.into_inner(); + Box::pin(api::server_wrap( + flow.clone(), + state, + &http_req, + req_payload.clone(), + |state, _: auth::UserFromToken, req| core::get_action_url(state, req), + &auth::JWTAuth(Permission::MerchantAccountWrite), + api_locking::LockAction::NotApplicable, + )) + .await +} + +pub async fn sync_onboarding_status( + state: web::Data, + http_req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::SyncOnboardingStatus; + let req_payload = json_payload.into_inner(); + Box::pin(api::server_wrap( + flow.clone(), + state, + &http_req, + req_payload.clone(), + core::sync_onboarding_status, + &auth::JWTAuth(Permission::MerchantAccountWrite), + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/routes/fraud_check.rs b/crates/router/src/routes/fraud_check.rs new file mode 100644 index 000000000000..d4363a236bb3 --- /dev/null +++ b/crates/router/src/routes/fraud_check.rs @@ -0,0 +1,42 @@ +use actix_web::{web, HttpRequest, HttpResponse}; +use common_utils::events::{ApiEventMetric, ApiEventsType}; +use router_env::Flow; + +use crate::{ + core::{api_locking, fraud_check as frm_core}, + services::{self, api}, + types::fraud_check::FraudCheckResponseData, + AppState, +}; + +pub async fn frm_fulfillment( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::FrmFulfillment; + Box::pin(api::server_wrap( + flow, + state.clone(), + &req, + json_payload.into_inner(), + |state, auth, req| { + frm_core::frm_fulfillment_core(state, auth.merchant_account, auth.key_store, req) + }, + &services::authentication::ApiKeyAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} + +impl ApiEventMetric for FraudCheckResponseData { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::FraudCheck) + } +} + +impl ApiEventMetric for frm_core::types::FrmFulfillmentRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::FraudCheck) + } +} diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 6aa2bbad0b15..f5519b960375 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -13,6 +13,7 @@ pub enum ApiIdentifier { Ephemeral, Mandates, PaymentMethods, + PaymentMethodAuth, Payouts, Disputes, CardsInfo, @@ -28,6 +29,7 @@ pub enum ApiIdentifier { Gsm, User, UserRole, + ConnectorOnboarding, } impl From for ApiIdentifier { @@ -85,6 +87,8 @@ impl From for ApiIdentifier { | Flow::PaymentMethodsDelete | Flow::ValidatePaymentMethod => Self::PaymentMethods, + Flow::PmAuthLinkTokenCreate | Flow::PmAuthExchangeToken => Self::PaymentMethodAuth, + Flow::PaymentsCreate | Flow::PaymentsRetrieve | Flow::PaymentsUpdate @@ -96,7 +100,8 @@ impl From for ApiIdentifier { | Flow::PaymentsSessionToken | Flow::PaymentsStart | Flow::PaymentsList - | Flow::PaymentsRedirect => Self::Payments, + | Flow::PaymentsRedirect + | Flow::PaymentsIncrementalAuthorization => Self::Payments, Flow::PayoutsCreate | Flow::PayoutsRetrieve @@ -110,7 +115,7 @@ impl From for ApiIdentifier { | Flow::RefundsUpdate | Flow::RefundsList => Self::Refunds, - Flow::IncomingWebhookReceive => Self::Webhooks, + Flow::FrmFulfillment | Flow::IncomingWebhookReceive => Self::Webhooks, Flow::ApiKeyCreate | Flow::ApiKeyRetrieve @@ -149,6 +154,8 @@ impl From for ApiIdentifier { | Flow::GsmRuleDelete => Self::Gsm, Flow::UserConnectAccount + | Flow::UserSignUp + | Flow::UserSignIn | Flow::ChangePassword | Flow::SetDashboardMetadata | Flow::GetMutltipleDashboardMetadata @@ -159,11 +166,18 @@ impl From for ApiIdentifier { | Flow::GenerateSampleData | Flow::DeleteSampleData | Flow::UserMerchantAccountList - | Flow::GetUserDetails => Self::User, + | Flow::GetUserDetails + | Flow::ForgotPassword + | Flow::ResetPassword + | Flow::InviteUser + | Flow::UserSignUpWithMerchantId + | Flow::VerifyEmail => Self::User, Flow::ListRoles | Flow::GetRole | Flow::UpdateUserRole | Flow::GetAuthorizationInfo => { Self::UserRole } + + Flow::GetActionUrl | Flow::SyncOnboardingStatus => Self::ConnectorOnboarding, } } } diff --git a/crates/router/src/routes/metrics.rs b/crates/router/src/routes/metrics.rs index a8e6f9d2a892..192df1a09298 100644 --- a/crates/router/src/routes/metrics.rs +++ b/crates/router/src/routes/metrics.rs @@ -85,6 +85,7 @@ counter_metric!(CONNECTOR_HTTP_STATUS_CODE_5XX_COUNT, GLOBAL_METER); // Service Level counter_metric!(CARD_LOCKER_FAILURES, GLOBAL_METER); +counter_metric!(CARD_LOCKER_SUCCESSFUL_RESPONSE, GLOBAL_METER); counter_metric!(TEMP_LOCKER_FAILURES, GLOBAL_METER); histogram_metric!(CARD_ADD_TIME, GLOBAL_METER); histogram_metric!(CARD_GET_TIME, GLOBAL_METER); diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 979b15a3d7f2..b836f02cded2 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -907,6 +907,122 @@ pub async fn get_filters_for_payments( ) .await } + +#[cfg(feature = "oltp")] +#[instrument(skip_all, fields(flow = ?Flow::PaymentsApprove, payment_id))] +// #[post("/{payment_id}/approve")] +pub async fn payments_approve( + state: web::Data, + http_req: actix_web::HttpRequest, + json_payload: web::Json, + path: web::Path, +) -> impl Responder { + let mut payload = json_payload.into_inner(); + let payment_id = path.into_inner(); + tracing::Span::current().record("payment_id", &payment_id); + payload.payment_id = payment_id; + let flow = Flow::PaymentsApprove; + let fpayload = FPaymentsApproveRequest(&payload); + let locking_action = fpayload.get_locking_input(flow.clone()); + + Box::pin(api::server_wrap( + flow.clone(), + state, + &http_req, + payload.clone(), + |state, auth, req| { + payments::payments_core::< + api_types::Authorize, + payment_types::PaymentsResponse, + _, + _, + _, + Oss, + >( + state, + auth.merchant_account, + auth.key_store, + payments::PaymentApprove, + payment_types::PaymentsRequest { + payment_id: Some(payment_types::PaymentIdType::PaymentIntentId( + req.payment_id, + )), + ..Default::default() + }, + api::AuthFlow::Merchant, + payments::CallConnectorAction::Trigger, + None, + payment_types::HeaderPayload::default(), + ) + }, + match env::which() { + env::Env::Production => &auth::ApiKeyAuth, + _ => auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::PaymentWrite), + http_req.headers(), + ), + }, + locking_action, + )) + .await +} + +#[cfg(feature = "oltp")] +#[instrument(skip_all, fields(flow = ?Flow::PaymentsReject, payment_id))] +// #[post("/{payment_id}/reject")] +pub async fn payments_reject( + state: web::Data, + http_req: actix_web::HttpRequest, + json_payload: web::Json, + path: web::Path, +) -> impl Responder { + let mut payload = json_payload.into_inner(); + let payment_id = path.into_inner(); + tracing::Span::current().record("payment_id", &payment_id); + payload.payment_id = payment_id; + let flow = Flow::PaymentsReject; + let fpayload = FPaymentsRejectRequest(&payload); + let locking_action = fpayload.get_locking_input(flow.clone()); + + Box::pin(api::server_wrap( + flow.clone(), + state, + &http_req, + payload.clone(), + |state, auth, req| { + payments::payments_core::< + api_types::Reject, + payment_types::PaymentsResponse, + _, + _, + _, + Oss, + >( + state, + auth.merchant_account, + auth.key_store, + payments::PaymentReject, + req, + api::AuthFlow::Merchant, + payments::CallConnectorAction::Trigger, + None, + payment_types::HeaderPayload::default(), + ) + }, + match env::which() { + env::Env::Production => &auth::ApiKeyAuth, + _ => auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::PaymentWrite), + http_req.headers(), + ), + }, + locking_action, + )) + .await +} + async fn authorize_verify_select( operation: Op, state: app::AppState, @@ -986,6 +1102,67 @@ where } } +/// Payments - Incremental Authorization +/// +/// Authorized amount for a payment can be incremented if it is in status: requires_capture +#[utoipa::path( + post, + path = "/payments/{payment_id}/incremental_authorization", + request_body=PaymentsIncrementalAuthorizationRequest, + params( + ("payment_id" = String, Path, description = "The identifier for payment") + ), + responses( + (status = 200, description = "Payment authorized amount incremented"), + (status = 400, description = "Missing mandatory fields") + ), + tag = "Payments", + operation_id = "Increment authorized amount for a Payment", + security(("api_key" = [])) +)] +#[instrument(skip_all, fields(flow = ?Flow::PaymentsIncrementalAuthorization))] +pub async fn payments_incremental_authorization( + state: web::Data, + req: actix_web::HttpRequest, + json_payload: web::Json, + path: web::Path, +) -> impl Responder { + let flow = Flow::PaymentsIncrementalAuthorization; + let mut payload = json_payload.into_inner(); + let payment_id = path.into_inner(); + payload.payment_id = payment_id; + let locking_action = payload.get_locking_input(flow.clone()); + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth, req| { + payments::payments_core::< + api_types::IncrementalAuthorization, + payment_types::PaymentsResponse, + _, + _, + _, + Oss, + >( + state, + auth.merchant_account, + auth.key_store, + payments::PaymentIncrementalAuthorization, + req, + api::AuthFlow::Merchant, + payments::CallConnectorAction::Trigger, + None, + HeaderPayload::default(), + ) + }, + &auth::ApiKeyAuth, + locking_action, + )) + .await +} + pub fn get_or_generate_payment_id( payload: &mut payment_types::PaymentsRequest, ) -> errors::RouterResult<()> { @@ -1135,3 +1312,55 @@ impl GetLockingInput for payment_types::PaymentsCaptureRequest { } } } + +struct FPaymentsApproveRequest<'a>(&'a payment_types::PaymentsApproveRequest); + +impl<'a> GetLockingInput for FPaymentsApproveRequest<'a> { + fn get_locking_input(&self, flow: F) -> api_locking::LockAction + where + F: types::FlowMetric, + lock_utils::ApiIdentifier: From, + { + api_locking::LockAction::Hold { + input: api_locking::LockingInput { + unique_locking_key: self.0.payment_id.to_owned(), + api_identifier: lock_utils::ApiIdentifier::from(flow), + override_lock_retries: None, + }, + } + } +} + +struct FPaymentsRejectRequest<'a>(&'a payment_types::PaymentsRejectRequest); + +impl<'a> GetLockingInput for FPaymentsRejectRequest<'a> { + fn get_locking_input(&self, flow: F) -> api_locking::LockAction + where + F: types::FlowMetric, + lock_utils::ApiIdentifier: From, + { + api_locking::LockAction::Hold { + input: api_locking::LockingInput { + unique_locking_key: self.0.payment_id.to_owned(), + api_identifier: lock_utils::ApiIdentifier::from(flow), + override_lock_retries: None, + }, + } + } +} + +impl GetLockingInput for payment_types::PaymentsIncrementalAuthorizationRequest { + fn get_locking_input(&self, flow: F) -> api_locking::LockAction + where + F: types::FlowMetric, + lock_utils::ApiIdentifier: From, + { + api_locking::LockAction::Hold { + input: api_locking::LockingInput { + unique_locking_key: self.payment_id.to_owned(), + api_identifier: lock_utils::ApiIdentifier::from(flow), + override_lock_retries: None, + }, + } + } +} diff --git a/crates/router/src/routes/pm_auth.rs b/crates/router/src/routes/pm_auth.rs new file mode 100644 index 000000000000..cfadd787c310 --- /dev/null +++ b/crates/router/src/routes/pm_auth.rs @@ -0,0 +1,73 @@ +use actix_web::{web, HttpRequest, Responder}; +use api_models as api_types; +use router_env::{instrument, tracing, types::Flow}; + +use crate::{core::api_locking, routes::AppState, services::api as oss_api}; + +#[instrument(skip_all, fields(flow = ?Flow::PmAuthLinkTokenCreate))] +pub async fn link_token_create( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> impl Responder { + let payload = json_payload.into_inner(); + let flow = Flow::PmAuthLinkTokenCreate; + let (auth, _) = match crate::services::authentication::check_client_secret_and_get_auth( + req.headers(), + &payload, + ) { + Ok((auth, _auth_flow)) => (auth, _auth_flow), + Err(e) => return oss_api::log_and_return_error_response(e), + }; + Box::pin(oss_api::server_wrap( + flow, + state, + &req, + payload, + |state, auth, payload| { + crate::core::pm_auth::create_link_token( + state, + auth.merchant_account, + auth.key_store, + payload, + ) + }, + &*auth, + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[instrument(skip_all, fields(flow = ?Flow::PmAuthExchangeToken))] +pub async fn exchange_token( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> impl Responder { + let payload = json_payload.into_inner(); + let flow = Flow::PmAuthExchangeToken; + let (auth, _) = match crate::services::authentication::check_client_secret_and_get_auth( + req.headers(), + &payload, + ) { + Ok((auth, _auth_flow)) => (auth, _auth_flow), + Err(e) => return oss_api::log_and_return_error_response(e), + }; + Box::pin(oss_api::server_wrap( + flow, + state, + &req, + payload, + |state, auth, payload| { + crate::core::pm_auth::exchange_token_core( + state, + auth.merchant_account, + auth.key_store, + payload, + ) + }, + &*auth, + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index 97bd7054da9e..594da67aa023 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -19,6 +19,65 @@ use crate::{ utils::user::dashboard_metadata::{parse_string_to_enums, set_ip_address_if_required}, }; +#[cfg(feature = "email")] +pub async fn user_signup_with_merchant_id( + state: web::Data, + http_req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::UserSignUpWithMerchantId; + let req_payload = json_payload.into_inner(); + Box::pin(api::server_wrap( + flow.clone(), + state, + &http_req, + req_payload.clone(), + |state, _, req_body| user_core::signup_with_merchant_id(state, req_body), + &auth::NoAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} + +pub async fn user_signup( + state: web::Data, + http_req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::UserSignUp; + let req_payload = json_payload.into_inner(); + Box::pin(api::server_wrap( + flow.clone(), + state, + &http_req, + req_payload.clone(), + |state, _, req_body| user_core::signup(state, req_body), + &auth::NoAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} + +pub async fn user_signin( + state: web::Data, + http_req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::UserSignIn; + let req_payload = json_payload.into_inner(); + Box::pin(api::server_wrap( + flow.clone(), + state, + &http_req, + req_payload.clone(), + |state, _, req_body| user_core::signin(state, req_body), + &auth::NoAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[cfg(feature = "email")] pub async fn user_connect_account( state: web::Data, http_req: HttpRequest, @@ -56,7 +115,7 @@ pub async fn change_password( .await } -pub async fn set_merchant_scoped_dashboard_metadata( +pub async fn set_dashboard_metadata( state: web::Data, req: HttpRequest, json_payload: web::Json, @@ -235,3 +294,79 @@ pub async fn get_user_details(state: web::Data, req: HttpRequest) -> H )) .await } + +#[cfg(feature = "email")] +pub async fn forgot_password( + state: web::Data, + req: HttpRequest, + payload: web::Json, +) -> HttpResponse { + let flow = Flow::ForgotPassword; + Box::pin(api::server_wrap( + flow, + state.clone(), + &req, + payload.into_inner(), + |state, _, payload| user_core::forgot_password(state, payload), + &auth::NoAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[cfg(feature = "email")] +pub async fn reset_password( + state: web::Data, + req: HttpRequest, + payload: web::Json, +) -> HttpResponse { + let flow = Flow::ResetPassword; + Box::pin(api::server_wrap( + flow, + state.clone(), + &req, + payload.into_inner(), + |state, _, payload| user_core::reset_password(state, payload), + &auth::NoAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[cfg(feature = "email")] +pub async fn invite_user( + state: web::Data, + req: HttpRequest, + payload: web::Json, +) -> HttpResponse { + let flow = Flow::InviteUser; + Box::pin(api::server_wrap( + flow, + state.clone(), + &req, + payload.into_inner(), + |state, user, payload| user_core::invite_user(state, payload, user), + &auth::JWTAuth(Permission::UsersWrite), + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[cfg(feature = "email")] +pub async fn verify_email( + state: web::Data, + http_req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::VerifyEmail; + Box::pin(api::server_wrap( + flow.clone(), + state, + &http_req, + json_payload.into_inner(), + |state, _, req_payload| user_core::verify_email(state, req_payload), + &auth::NoAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/services.rs b/crates/router/src/services.rs index e46612b95dfc..57f3b802bd5d 100644 --- a/crates/router/src/services.rs +++ b/crates/router/src/services.rs @@ -6,6 +6,7 @@ pub mod encryption; pub mod jwt; pub mod kafka; pub mod logger; +pub mod pm_auth; #[cfg(feature = "email")] pub mod email; diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 8734b878f062..99b6d61e3017 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -587,6 +587,21 @@ pub async fn send_request( None => client, } } + Method::Patch => { + let client = client.patch(url); + match request.body { + Some(RequestContent::Json(payload)) => client.json(&payload), + Some(RequestContent::FormData(form)) => client.multipart(form), + Some(RequestContent::FormUrlEncoded(payload)) => client.form(&payload), + Some(RequestContent::Xml(payload)) => { + let body = quick_xml::se::to_string(&payload) + .into_report() + .change_context(errors::ApiClientError::BodySerializationFailed)?; + client.body(body).header("Content-Type", "application/xml") + } + None => client, + } + } Method::Delete => client.delete(url), } .add_headers(headers) @@ -1228,7 +1243,10 @@ impl Authenticate for api_models::payments::PaymentsSessionRequest { impl Authenticate for api_models::payments::PaymentsRetrieveRequest {} impl Authenticate for api_models::payments::PaymentsCancelRequest {} impl Authenticate for api_models::payments::PaymentsCaptureRequest {} +impl Authenticate for api_models::payments::PaymentsIncrementalAuthorizationRequest {} impl Authenticate for api_models::payments::PaymentsStartRequest {} +// impl Authenticate for api_models::payments::PaymentsApproveRequest {} +impl Authenticate for api_models::payments::PaymentsRejectRequest {} pub fn build_redirection_form( form: &RedirectForm, diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index 8a0cd7c729e9..b48465ebd174 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -641,6 +641,18 @@ impl ClientSecretFetch for api_models::payments::RetrievePaymentLinkRequest { } } +impl ClientSecretFetch for api_models::pm_auth::LinkTokenCreateRequest { + fn get_client_secret(&self) -> Option<&String> { + self.client_secret.as_ref() + } +} + +impl ClientSecretFetch for api_models::pm_auth::ExchangeTokenCreateRequest { + fn get_client_secret(&self) -> Option<&String> { + self.client_secret.as_ref() + } +} + pub fn get_auth_type_and_flow( headers: &HeaderMap, ) -> RouterResult<( diff --git a/crates/router/src/services/email/types.rs b/crates/router/src/services/email/types.rs index a4a4681c6001..9e26c45ba6b1 100644 --- a/crates/router/src/services/email/types.rs +++ b/crates/router/src/services/email/types.rs @@ -50,7 +50,7 @@ pub mod html { #[derive(serde::Serialize, serde::Deserialize)] pub struct EmailToken { email: String, - expiration: u64, + exp: u64, } impl EmailToken { @@ -59,13 +59,17 @@ impl EmailToken { settings: &configs::settings::Settings, ) -> CustomResult { let expiration_duration = std::time::Duration::from_secs(consts::EMAIL_TOKEN_TIME_IN_SECS); - let expiration = jwt::generate_exp(expiration_duration)?.as_secs(); + let exp = jwt::generate_exp(expiration_duration)?.as_secs(); let token_payload = Self { email: email.get_secret().expose(), - expiration, + exp, }; jwt::generate_jwt(&token_payload, settings).await } + + pub fn get_email(&self) -> &str { + self.email.as_str() + } } pub fn get_link_with_token( @@ -91,7 +95,7 @@ impl EmailData for VerifyEmail { .change_context(EmailError::TokenGenerationFailure)?; let verify_email_link = - get_link_with_token(&self.settings.server.base_url, token, "verify_email"); + get_link_with_token(&self.settings.email.base_url, token, "verify_email"); let body = html::get_html_body(EmailBody::Verify { link: verify_email_link, @@ -120,7 +124,7 @@ impl EmailData for ResetPassword { .change_context(EmailError::TokenGenerationFailure)?; let reset_password_link = - get_link_with_token(&self.settings.server.base_url, token, "set_password"); + get_link_with_token(&self.settings.email.base_url, token, "set_password"); let body = html::get_html_body(EmailBody::Reset { link: reset_password_link, @@ -149,7 +153,7 @@ impl EmailData for MagicLink { .await .change_context(EmailError::TokenGenerationFailure)?; - let magic_link_login = get_link_with_token(&self.settings.server.base_url, token, "login"); + let magic_link_login = get_link_with_token(&self.settings.email.base_url, token, "login"); let body = html::get_html_body(EmailBody::MagicLink { link: magic_link_login, @@ -179,7 +183,7 @@ impl EmailData for InviteUser { .change_context(EmailError::TokenGenerationFailure)?; let invite_user_link = - get_link_with_token(&self.settings.server.base_url, token, "set_password"); + get_link_with_token(&self.settings.email.base_url, token, "set_password"); let body = html::get_html_body(EmailBody::MagicLink { link: invite_user_link, diff --git a/crates/router/src/services/pm_auth.rs b/crates/router/src/services/pm_auth.rs new file mode 100644 index 000000000000..7487b12663b1 --- /dev/null +++ b/crates/router/src/services/pm_auth.rs @@ -0,0 +1,95 @@ +use pm_auth::{ + consts, + core::errors::ConnectorError, + types::{self as pm_auth_types, api::BoxedConnectorIntegration, PaymentAuthRouterData}, +}; + +use crate::{ + core::errors::{self}, + logger, + routes::AppState, + services::{self}, +}; + +pub async fn execute_connector_processing_step< + 'b, + 'a, + T: 'static, + Req: Clone + 'static, + Resp: Clone + 'static, +>( + state: &'b AppState, + connector_integration: BoxedConnectorIntegration<'a, T, Req, Resp>, + req: &'b PaymentAuthRouterData, + connector: &pm_auth_types::PaymentMethodAuthConnectors, +) -> errors::CustomResult, ConnectorError> +where + T: Clone, + Req: Clone, + Resp: Clone, +{ + let mut router_data = req.clone(); + + let connector_request = connector_integration.build_request(req, connector)?; + + match connector_request { + Some(request) => { + logger::debug!(connector_request=?request); + let response = services::api::call_connector_api(state, request).await; + logger::debug!(connector_response=?response); + match response { + Ok(body) => { + let response = match body { + Ok(body) => { + let body = pm_auth_types::Response { + headers: body.headers, + response: body.response, + status_code: body.status_code, + }; + let connector_http_status_code = Some(body.status_code); + let mut data = + connector_integration.handle_response(&router_data, body)?; + data.connector_http_status_code = connector_http_status_code; + + data + } + Err(body) => { + let body = pm_auth_types::Response { + headers: body.headers, + response: body.response, + status_code: body.status_code, + }; + router_data.connector_http_status_code = Some(body.status_code); + + let error = match body.status_code { + 500..=511 => connector_integration.get_5xx_error_response(body)?, + _ => connector_integration.get_error_response(body)?, + }; + + router_data.response = Err(error); + + router_data + } + }; + Ok(response) + } + Err(error) => { + if error.current_context().is_upstream_timeout() { + let error_response = pm_auth_types::ErrorResponse { + code: consts::REQUEST_TIMEOUT_ERROR_CODE.to_string(), + message: consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string(), + reason: Some(consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string()), + status_code: 504, + }; + router_data.response = Err(error_response); + router_data.connector_http_status_code = Some(504); + Ok(router_data) + } else { + Err(error.change_context(ConnectorError::ProcessingStepFailed(None))) + } + } + } + } + None => Ok(router_data), + } +} diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index c267a54cc57b..aa563c647eaa 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -8,6 +8,10 @@ pub mod api; pub mod domain; +#[cfg(feature = "frm")] +pub mod fraud_check; +pub mod pm_auth; + pub mod storage; pub mod transformers; @@ -22,6 +26,7 @@ use common_utils::{pii, pii::Email}; use data_models::mandates::MandateData; use error_stack::{IntoReport, ResultExt}; use masking::Secret; +use serde::Serialize; use self::{api::payments, storage::enums as storage_enums}; pub use crate::core::payments::{CustomerDetails, PaymentAddress}; @@ -30,7 +35,7 @@ use crate::core::utils::IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_DISPUTE_FLO use crate::{ core::{ errors::{self, RouterResult}, - payments::{PaymentData, RecurringMandatePaymentData}, + payments::{types, PaymentData, RecurringMandatePaymentData}, }, services, types::{storage::payment_attempt::PaymentAttemptExt, transformers::ForeignFrom}, @@ -52,6 +57,11 @@ pub type PaymentsBalanceRouterData = pub type PaymentsSyncRouterData = RouterData; pub type PaymentsCaptureRouterData = RouterData; +pub type PaymentsIncrementalAuthorizationRouterData = RouterData< + api::IncrementalAuthorization, + PaymentsIncrementalAuthorizationData, + PaymentsResponseData, +>; pub type PaymentsCancelRouterData = RouterData; pub type PaymentsRejectRouterData = RouterData; @@ -142,6 +152,11 @@ pub type TokenizationType = dyn services::ConnectorIntegration< PaymentMethodTokenizationData, PaymentsResponseData, >; +pub type IncrementalAuthorizationType = dyn services::ConnectorIntegration< + api::IncrementalAuthorization, + PaymentsIncrementalAuthorizationData, + PaymentsResponseData, +>; pub type ConnectorCustomerType = dyn services::ConnectorIntegration< api::CreateConnectorCustomer, @@ -379,7 +394,7 @@ pub struct PaymentsAuthorizeData { pub related_transaction_id: Option, pub payment_experience: Option, pub payment_method_type: Option, - pub surcharge_details: Option, + pub surcharge_details: Option, pub customer_id: Option, pub request_incremental_authorization: bool, } @@ -395,6 +410,15 @@ pub struct PaymentsCaptureData { pub browser_info: Option, } +#[derive(Debug, Clone, Default)] +pub struct PaymentsIncrementalAuthorizationData { + pub total_amount: i64, + pub additional_amount: i64, + pub currency: storage_enums::Currency, + pub reason: Option, + pub connector_transaction_id: String, +} + #[allow(dead_code)] #[derive(Debug, Clone, Default)] pub struct MultipleCaptureRequestData { @@ -441,7 +465,7 @@ pub struct PaymentsPreProcessingData { pub router_return_url: Option, pub webhook_url: Option, pub complete_authorize_url: Option, - pub surcharge_details: Option, + pub surcharge_details: Option, pub browser_info: Option, pub connector_transaction_id: Option, } @@ -517,7 +541,7 @@ pub struct PaymentsSessionData { pub amount: i64, pub currency: storage_enums::Currency, pub country: Option, - pub surcharge_details: Option, + pub surcharge_details: Option, pub order_details: Option>, } @@ -599,6 +623,7 @@ impl Capturable for PaymentsCancelData { impl Capturable for PaymentsApproveData {} impl Capturable for PaymentsRejectData {} impl Capturable for PaymentsSessionData {} +impl Capturable for PaymentsIncrementalAuthorizationData {} impl Capturable for PaymentsSyncData { fn get_capture_amount(&self, payment_data: &PaymentData) -> Option where @@ -707,6 +732,12 @@ pub enum PaymentsResponseData { session_token: Option, connector_response_reference_id: Option, }, + IncrementalAuthorizationResponse { + status: common_enums::AuthorizationStatus, + connector_authorization_id: Option, + error_code: Option, + error_message: Option, + }, } #[derive(Debug, Clone)] @@ -715,7 +746,7 @@ pub enum PreprocessingResponseId { ConnectorTransactionId(String), } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, Serialize)] pub enum ResponseId { ConnectorTransactionId(String), EncodedData(String), diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 96bcaca3ed5d..978ce078faf9 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -1,11 +1,15 @@ pub mod admin; pub mod api_keys; pub mod configs; +#[cfg(feature = "olap")] +pub mod connector_onboarding; pub mod customers; pub mod disputes; pub mod enums; pub mod ephemeral_key; pub mod files; +#[cfg(feature = "frm")] +pub mod fraud_check; pub mod mandates; pub mod payment_link; pub mod payment_methods; @@ -19,9 +23,10 @@ pub mod webhooks; use std::{fmt::Debug, str::FromStr}; -use api_models::payment_methods::{SurchargeDetailsResponse, SurchargeMetadata}; use error_stack::{report, IntoReport, ResultExt}; +#[cfg(feature = "frm")] +pub use self::fraud_check::*; pub use self::{ admin::*, api_keys::*, configs::*, customers::*, disputes::*, files::*, payment_link::*, payment_methods::*, payments::*, payouts::*, refunds::*, webhooks::*, @@ -30,7 +35,10 @@ use super::ErrorResponse; use crate::{ configs::settings::Connectors, connector, consts, - core::errors::{self, CustomResult}, + core::{ + errors::{self, CustomResult}, + payments::types as payments_types, + }, services::{request, ConnectorIntegration, ConnectorRedirectResponse, ConnectorValidation}, types::{self, api::enums as api_enums}, }; @@ -150,6 +158,7 @@ pub trait Connector: + ConnectorTransactionId + Payouts + ConnectorVerifyWebhookSource + + FraudCheck { } @@ -169,7 +178,8 @@ impl< + FileUpload + ConnectorTransactionId + Payouts - + ConnectorVerifyWebhookSource, + + ConnectorVerifyWebhookSource + + FraudCheck, > Connector for T { } @@ -222,9 +232,9 @@ pub struct SessionConnectorData { /// Session Surcharge type pub enum SessionSurchargeDetails { /// Surcharge is calculated by hyperswitch - Calculated(SurchargeMetadata), + Calculated(payments_types::SurchargeMetadata), /// Surcharge is sent by merchant - PreDetermined(SurchargeDetailsResponse), + PreDetermined(payments_types::SurchargeDetails), } impl SessionSurchargeDetails { @@ -233,10 +243,14 @@ impl SessionSurchargeDetails { payment_method: &enums::PaymentMethod, payment_method_type: &enums::PaymentMethodType, card_network: Option<&enums::CardNetwork>, - ) -> Option { + ) -> Option { match self { Self::Calculated(surcharge_metadata) => surcharge_metadata - .get_surcharge_details(payment_method, payment_method_type, card_network) + .get_surcharge_details(payments_types::SurchargeKey::PaymentMethodData( + *payment_method, + *payment_method_type, + card_network.cloned(), + )) .cloned(), Self::PreDetermined(surcharge_details) => Some(surcharge_details.clone()), } @@ -404,6 +418,20 @@ impl ConnectorData { } } +#[cfg(feature = "frm")] +pub trait FraudCheck: + ConnectorCommon + + FraudCheckSale + + FraudCheckTransaction + + FraudCheckCheckout + + FraudCheckFulfillment + + FraudCheckRecordReturn +{ +} + +#[cfg(not(feature = "frm"))] +pub trait FraudCheck {} + #[cfg(test)] mod test { #![allow(clippy::unwrap_used)] diff --git a/crates/router/src/types/api/connector_onboarding.rs b/crates/router/src/types/api/connector_onboarding.rs new file mode 100644 index 000000000000..5b1d581a20ef --- /dev/null +++ b/crates/router/src/types/api/connector_onboarding.rs @@ -0,0 +1 @@ +pub mod paypal; diff --git a/crates/router/src/types/api/connector_onboarding/paypal.rs b/crates/router/src/types/api/connector_onboarding/paypal.rs new file mode 100644 index 000000000000..0cc026d4d7ad --- /dev/null +++ b/crates/router/src/types/api/connector_onboarding/paypal.rs @@ -0,0 +1,247 @@ +use api_models::connector_onboarding as api; +use error_stack::{IntoReport, ResultExt}; + +use crate::core::errors::{ApiErrorResponse, RouterResult}; + +#[derive(serde::Deserialize, Debug)] +pub struct HateoasLink { + pub href: String, + pub rel: String, + pub method: String, +} + +#[derive(serde::Deserialize, Debug)] +pub struct PartnerReferralResponse { + pub links: Vec, +} + +#[derive(serde::Serialize, Debug)] +pub struct PartnerReferralRequest { + pub tracking_id: String, + pub operations: Vec, + pub products: Vec, + pub capabilities: Vec, + pub partner_config_override: PartnerConfigOverride, + pub legal_consents: Vec, +} + +#[derive(serde::Serialize, Debug)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PayPalProducts { + Ppcp, + AdvancedVaulting, +} + +#[derive(serde::Serialize, Debug)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PayPalCapabilities { + PaypalWalletVaultingAdvanced, +} + +#[derive(serde::Serialize, Debug)] +pub struct PartnerReferralOperations { + pub operation: PayPalReferralOperationType, + pub api_integration_preference: PartnerReferralIntegrationPreference, +} + +#[derive(serde::Serialize, Debug)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PayPalReferralOperationType { + ApiIntegration, +} + +#[derive(serde::Serialize, Debug)] +pub struct PartnerReferralIntegrationPreference { + pub rest_api_integration: PartnerReferralRestApiIntegration, +} + +#[derive(serde::Serialize, Debug)] +pub struct PartnerReferralRestApiIntegration { + pub integration_method: IntegrationMethod, + pub integration_type: PayPalIntegrationType, + pub third_party_details: PartnerReferralThirdPartyDetails, +} + +#[derive(serde::Serialize, Debug)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum IntegrationMethod { + Paypal, +} + +#[derive(serde::Serialize, Debug)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PayPalIntegrationType { + ThirdParty, +} + +#[derive(serde::Serialize, Debug)] +pub struct PartnerReferralThirdPartyDetails { + pub features: Vec, +} + +#[derive(serde::Serialize, Debug)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PayPalFeatures { + Payment, + Refund, + Vault, + AccessMerchantInformation, + BillingAgreement, + ReadSellerDispute, +} + +#[derive(serde::Serialize, Debug)] +pub struct PartnerConfigOverride { + pub partner_logo_url: String, + pub return_url: String, +} + +#[derive(serde::Serialize, Debug)] +pub struct LegalConsent { + #[serde(rename = "type")] + pub consent_type: LegalConsentType, + pub granted: bool, +} + +#[derive(serde::Serialize, Debug)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum LegalConsentType { + ShareDataConsent, +} + +impl PartnerReferralRequest { + pub fn new(tracking_id: String, return_url: String) -> Self { + Self { + tracking_id, + operations: vec![PartnerReferralOperations { + operation: PayPalReferralOperationType::ApiIntegration, + api_integration_preference: PartnerReferralIntegrationPreference { + rest_api_integration: PartnerReferralRestApiIntegration { + integration_method: IntegrationMethod::Paypal, + integration_type: PayPalIntegrationType::ThirdParty, + third_party_details: PartnerReferralThirdPartyDetails { + features: vec![ + PayPalFeatures::Payment, + PayPalFeatures::Refund, + PayPalFeatures::Vault, + PayPalFeatures::AccessMerchantInformation, + PayPalFeatures::BillingAgreement, + PayPalFeatures::ReadSellerDispute, + ], + }, + }, + }, + }], + products: vec![PayPalProducts::Ppcp, PayPalProducts::AdvancedVaulting], + capabilities: vec![PayPalCapabilities::PaypalWalletVaultingAdvanced], + partner_config_override: PartnerConfigOverride { + partner_logo_url: "https://hyperswitch.io/img/websiteIcon.svg".to_string(), + return_url, + }, + legal_consents: vec![LegalConsent { + consent_type: LegalConsentType::ShareDataConsent, + granted: true, + }], + } + } +} + +#[derive(serde::Deserialize, Debug)] +pub struct SellerStatusResponse { + pub merchant_id: String, + pub links: Vec, +} + +#[derive(serde::Deserialize, Debug)] +pub struct SellerStatusDetailsResponse { + pub merchant_id: String, + pub primary_email_confirmed: bool, + pub payments_receivable: bool, + pub products: Vec, +} + +#[derive(serde::Deserialize, Debug)] +pub struct SellerStatusProducts { + pub name: String, + pub vetting_status: Option, +} + +#[derive(serde::Deserialize, Debug, Clone)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum VettingStatus { + NeedMoreData, + Subscribed, + Denied, +} + +impl SellerStatusResponse { + pub fn extract_merchant_details_url(self, paypal_base_url: &str) -> RouterResult { + self.links + .get(0) + .and_then(|link| link.href.strip_prefix('/')) + .map(|link| format!("{}{}", paypal_base_url, link)) + .ok_or(ApiErrorResponse::InternalServerError) + .into_report() + .attach_printable("Merchant details not received in onboarding status") + } +} + +impl SellerStatusDetailsResponse { + pub fn check_payments_receivable(&self) -> Option { + if !self.payments_receivable { + return Some(api::PayPalOnboardingStatus::PaymentsNotReceivable); + } + None + } + + pub fn check_ppcp_custom_status(&self) -> Option { + match self.get_ppcp_custom_status() { + Some(VettingStatus::Denied) => Some(api::PayPalOnboardingStatus::PpcpCustomDenied), + Some(VettingStatus::Subscribed) => None, + _ => Some(api::PayPalOnboardingStatus::MorePermissionsNeeded), + } + } + + fn check_email_confirmation(&self) -> Option { + if !self.primary_email_confirmed { + return Some(api::PayPalOnboardingStatus::EmailNotVerified); + } + None + } + + pub async fn get_eligibility_status(&self) -> RouterResult { + Ok(self + .check_payments_receivable() + .or(self.check_email_confirmation()) + .or(self.check_ppcp_custom_status()) + .unwrap_or(api::PayPalOnboardingStatus::Success( + api::PayPalOnboardingDone { + payer_id: self.get_payer_id(), + }, + ))) + } + + fn get_ppcp_custom_status(&self) -> Option { + self.products + .iter() + .find(|product| product.name == "PPCP_CUSTOM") + .and_then(|ppcp_custom| ppcp_custom.vetting_status.clone()) + } + + fn get_payer_id(&self) -> String { + self.merchant_id.to_string() + } +} + +impl PartnerReferralResponse { + pub fn extract_action_url(self) -> RouterResult { + Ok(self + .links + .into_iter() + .find(|hateoas_link| hateoas_link.rel == "action_url") + .ok_or(ApiErrorResponse::InternalServerError) + .into_report() + .attach_printable("Failed to get action_url from paypal response")? + .href) + } +} diff --git a/crates/router/src/types/api/fraud_check.rs b/crates/router/src/types/api/fraud_check.rs new file mode 100644 index 000000000000..7be60bfee952 --- /dev/null +++ b/crates/router/src/types/api/fraud_check.rs @@ -0,0 +1,91 @@ +use std::str::FromStr; + +use api_models::enums; +use common_utils::errors::CustomResult; +use error_stack::{IntoReport, ResultExt}; + +use super::{BoxedConnector, ConnectorData, SessionConnectorData}; +use crate::{ + connector, + core::errors, + services::api, + types::fraud_check::{ + FraudCheckCheckoutData, FraudCheckFulfillmentData, FraudCheckRecordReturnData, + FraudCheckResponseData, FraudCheckSaleData, FraudCheckTransactionData, + }, +}; + +#[derive(Debug, Clone)] +pub struct Sale; + +pub trait FraudCheckSale: + api::ConnectorIntegration +{ +} + +#[derive(Debug, Clone)] +pub struct Checkout; + +pub trait FraudCheckCheckout: + api::ConnectorIntegration +{ +} + +#[derive(Debug, Clone)] +pub struct Transaction; + +pub trait FraudCheckTransaction: + api::ConnectorIntegration +{ +} + +#[derive(Debug, Clone)] +pub struct Fulfillment; + +pub trait FraudCheckFulfillment: + api::ConnectorIntegration +{ +} + +#[derive(Debug, Clone)] +pub struct RecordReturn; + +pub trait FraudCheckRecordReturn: + api::ConnectorIntegration +{ +} + +#[derive(Clone, Debug)] +pub struct FraudCheckConnectorData { + pub connector: BoxedConnector, + pub connector_name: enums::FrmConnectors, +} +pub enum ConnectorCallType { + PreDetermined(ConnectorData), + Retryable(Vec), + SessionMultiple(Vec), +} + +impl FraudCheckConnectorData { + pub fn get_connector_by_name(name: &str) -> CustomResult { + let connector_name = enums::FrmConnectors::from_str(name) + .into_report() + .change_context(errors::ApiErrorResponse::IncorrectConnectorNameGiven) + .attach_printable_lazy(|| { + format!("unable to parse connector: {:?}", name.to_string()) + })?; + let connector = Self::convert_connector(connector_name)?; + Ok(Self { + connector, + connector_name, + }) + } + + fn convert_connector( + connector_name: enums::FrmConnectors, + ) -> CustomResult { + match connector_name { + enums::FrmConnectors::Signifyd => Ok(Box::new(&connector::Signifyd)), + } + } +} diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index b00a7f0cbdac..2acf42fa479d 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -5,11 +5,12 @@ pub use api_models::payments::{ PayLaterData, PaymentIdType, PaymentListConstraints, PaymentListFilterConstraints, PaymentListFilters, PaymentListResponse, PaymentListResponseV2, PaymentMethodData, PaymentMethodDataResponse, PaymentOp, PaymentRetrieveBody, PaymentRetrieveBodyWithCredentials, - PaymentsApproveRequest, PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsRedirectRequest, - PaymentsRedirectionResponse, PaymentsRejectRequest, PaymentsRequest, PaymentsResponse, - PaymentsResponseForm, PaymentsRetrieveRequest, PaymentsSessionRequest, PaymentsSessionResponse, - PaymentsStartRequest, PgRedirectResponse, PhoneDetails, RedirectionResponse, SessionToken, - TimeRange, UrlDetails, VerifyRequest, VerifyResponse, WalletData, + PaymentsApproveRequest, PaymentsCancelRequest, PaymentsCaptureRequest, + PaymentsIncrementalAuthorizationRequest, PaymentsRedirectRequest, PaymentsRedirectionResponse, + PaymentsRejectRequest, PaymentsRequest, PaymentsResponse, PaymentsResponseForm, + PaymentsRetrieveRequest, PaymentsSessionRequest, PaymentsSessionResponse, PaymentsStartRequest, + PgRedirectResponse, PhoneDetails, RedirectionResponse, SessionToken, TimeRange, UrlDetails, + VerifyRequest, VerifyResponse, WalletData, }; use error_stack::{IntoReport, ResultExt}; @@ -81,6 +82,9 @@ pub struct SetupMandate; #[derive(Debug, Clone)] pub struct PreProcessing; +#[derive(Debug, Clone)] +pub struct IncrementalAuthorization; + pub trait PaymentIdTypeExt { fn get_payment_intent_id(&self) -> errors::CustomResult; } @@ -164,6 +168,15 @@ pub trait MandateSetup: { } +pub trait PaymentIncrementalAuthorization: + api::ConnectorIntegration< + IncrementalAuthorization, + types::PaymentsIncrementalAuthorizationData, + types::PaymentsResponseData, +> +{ +} + pub trait PaymentsCompleteAuthorize: api::ConnectorIntegration< CompleteAuthorize, @@ -215,6 +228,7 @@ pub trait Payment: + PaymentToken + PaymentsPreProcessing + ConnectorCustomer + + PaymentIncrementalAuthorization { } diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index 082b29d80941..9bc27cba2b1d 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -26,7 +26,7 @@ use crate::{ db::StorageInterface, routes::AppState, services::{ - authentication::{AuthToken, UserFromToken}, + authentication::UserFromToken, authorization::{info, predefined_permissions}, }, types::transformers::ForeignFrom, @@ -215,6 +215,25 @@ impl NewUserOrganization { } } +impl TryFrom for NewUserOrganization { + type Error = error_stack::Report; + fn try_from(value: user_api::SignUpWithMerchantIdRequest) -> UserResult { + let new_organization = api_org::OrganizationNew::new(Some( + UserCompanyName::new(value.company_name)?.get_secret(), + )); + let db_organization = ForeignFrom::foreign_from(new_organization); + Ok(Self(db_organization)) + } +} + +impl From for NewUserOrganization { + fn from(_value: user_api::SignUpRequest) -> Self { + let new_organization = api_org::OrganizationNew::new(None); + let db_organization = ForeignFrom::foreign_from(new_organization); + Self(db_organization) + } +} + impl From for NewUserOrganization { fn from(_value: user_api::ConnectAccountRequest) -> Self { let new_organization = api_org::OrganizationNew::new(None); @@ -240,6 +259,15 @@ impl From for NewUserOrganization { } } +type InviteeUserRequestWithInvitedUserToken = (user_api::InviteUserRequest, UserFromToken); +impl From for NewUserOrganization { + fn from(_value: InviteeUserRequestWithInvitedUserToken) -> Self { + let new_organization = api_org::OrganizationNew::new(None); + let db_organization = ForeignFrom::foreign_from(new_organization); + Self(db_organization) + } +} + #[derive(Clone)] pub struct MerchantId(String); @@ -334,6 +362,24 @@ impl NewUserMerchant { } } +impl TryFrom for NewUserMerchant { + type Error = error_stack::Report; + + fn try_from(value: user_api::SignUpRequest) -> UserResult { + let merchant_id = MerchantId::new(format!( + "merchant_{}", + common_utils::date_time::now_unix_timestamp() + ))?; + let new_organization = NewUserOrganization::from(value); + + Ok(Self { + company_name: None, + merchant_id, + new_organization, + }) + } +} + impl TryFrom for NewUserMerchant { type Error = error_stack::Report; @@ -352,6 +398,21 @@ impl TryFrom for NewUserMerchant { } } +impl TryFrom for NewUserMerchant { + type Error = error_stack::Report; + fn try_from(value: user_api::SignUpWithMerchantIdRequest) -> UserResult { + let company_name = Some(UserCompanyName::new(value.company_name.clone())?); + let merchant_id = MerchantId::new(value.company_name.clone())?; + let new_organization = NewUserOrganization::try_from(value)?; + + Ok(Self { + company_name, + merchant_id, + new_organization, + }) + } +} + impl TryFrom for NewUserMerchant { type Error = error_stack::Report; @@ -368,6 +429,19 @@ impl TryFrom for NewUserMerchant { } } +impl TryFrom for NewUserMerchant { + type Error = error_stack::Report; + fn try_from(value: InviteeUserRequestWithInvitedUserToken) -> UserResult { + let merchant_id = MerchantId::new(value.clone().1.merchant_id)?; + let new_organization = NewUserOrganization::from(value); + Ok(Self { + company_name: None, + merchant_id, + new_organization, + }) + } +} + type UserMerchantCreateRequestWithToken = (UserFromStorage, user_api::UserMerchantCreate, UserFromToken); @@ -434,10 +508,23 @@ impl NewUser { .attach_printable("Error while inserting user") } + pub async fn check_if_already_exists_in_db(&self, state: AppState) -> UserResult<()> { + if state + .store + .find_user_by_email(self.get_email().into_inner().expose().expose().as_str()) + .await + .is_ok() + { + return Err(UserErrors::UserExists).into_report(); + } + Ok(()) + } + pub async fn insert_user_and_merchant_in_db( &self, state: AppState, ) -> UserResult { + self.check_if_already_exists_in_db(state.clone()).await?; let db = state.store.as_ref(); let merchant_id = self.get_new_merchant().get_merchant_id(); self.new_merchant @@ -495,6 +582,46 @@ impl TryFrom for storage_user::UserNew { } } +impl TryFrom for NewUser { + type Error = error_stack::Report; + + fn try_from(value: user_api::SignUpWithMerchantIdRequest) -> UserResult { + let email = value.email.clone().try_into()?; + let name = UserName::new(value.name.clone())?; + let password = UserPassword::new(value.password.clone())?; + let user_id = uuid::Uuid::new_v4().to_string(); + let new_merchant = NewUserMerchant::try_from(value)?; + + Ok(Self { + name, + email, + password, + user_id, + new_merchant, + }) + } +} + +impl TryFrom for NewUser { + type Error = error_stack::Report; + + fn try_from(value: user_api::SignUpRequest) -> UserResult { + let user_id = uuid::Uuid::new_v4().to_string(); + let email = value.email.clone().try_into()?; + let name = UserName::try_from(value.email.clone())?; + let password = UserPassword::new(value.password.clone())?; + let new_merchant = NewUserMerchant::try_from(value)?; + + Ok(Self { + user_id, + name, + email, + password, + new_merchant, + }) + } +} + impl TryFrom for NewUser { type Error = error_stack::Report; @@ -502,7 +629,7 @@ impl TryFrom for NewUser { let user_id = uuid::Uuid::new_v4().to_string(); let email = value.email.clone().try_into()?; let name = UserName::try_from(value.email.clone())?; - let password = UserPassword::new(value.password.clone())?; + let password = UserPassword::new(uuid::Uuid::new_v4().to_string().into())?; let new_merchant = NewUserMerchant::try_from(value)?; Ok(Self { @@ -552,6 +679,26 @@ impl TryFrom for NewUser { } } +impl TryFrom for NewUser { + type Error = error_stack::Report; + fn try_from(value: InviteeUserRequestWithInvitedUserToken) -> UserResult { + let user_id = uuid::Uuid::new_v4().to_string(); + let email = value.0.email.clone().try_into()?; + let name = UserName::new(value.0.name.clone())?; + let password = password::generate_password_hash(uuid::Uuid::new_v4().to_string().into())?; + let password = UserPassword::new(password)?; + let new_merchant = NewUserMerchant::try_from(value)?; + + Ok(Self { + user_id, + name, + email, + password, + new_merchant, + }) + } +} + #[derive(Clone)] pub struct UserFromStorage(pub storage_user::User); @@ -582,41 +729,6 @@ impl UserFromStorage { self.0.email.clone() } - pub async fn get_jwt_auth_token(&self, state: AppState, org_id: String) -> UserResult { - let role_id = self.get_role_from_db(state.clone()).await?.role_id; - let merchant_id = state - .store - .find_user_role_by_user_id(self.get_user_id()) - .await - .change_context(UserErrors::InternalServerError)? - .merchant_id; - AuthToken::new_token( - self.0.user_id.clone(), - merchant_id, - role_id, - &state.conf, - org_id, - ) - .await - } - - pub async fn get_jwt_auth_token_with_custom_merchant_id( - &self, - state: AppState, - merchant_id: String, - org_id: String, - ) -> UserResult { - let role_id = self.get_role_from_db(state.clone()).await?.role_id; - AuthToken::new_token( - self.0.user_id.clone(), - merchant_id, - role_id, - &state.conf, - org_id, - ) - .await - } - pub async fn get_role_from_db(&self, state: AppState) -> UserResult { state .store @@ -624,6 +736,29 @@ impl UserFromStorage { .await .change_context(UserErrors::InternalServerError) } + + #[cfg(feature = "email")] + pub fn get_verification_days_left(&self, state: AppState) -> UserResult> { + if self.0.is_verified { + return Ok(None); + } + + let allowed_unverified_duration = + time::Duration::days(state.conf.email.allowed_unverified_days); + + let user_created = self.0.created_at.date(); + let last_date_for_verification = user_created + .checked_add(allowed_unverified_duration) + .ok_or(UserErrors::InternalServerError)?; + + let today = common_utils::date_time::now().date(); + if today >= last_date_for_verification { + return Err(UserErrors::UnverifiedUser.into()); + } + + let days_left_for_verification = last_date_for_verification - today; + Ok(Some(days_left_for_verification.whole_days())) + } } impl TryFrom for user_role_api::ModuleInfo { diff --git a/crates/router/src/types/domain/user/dashboard_metadata.rs b/crates/router/src/types/domain/user/dashboard_metadata.rs index e65379346ac9..5e4017a3cb1a 100644 --- a/crates/router/src/types/domain/user/dashboard_metadata.rs +++ b/crates/router/src/types/domain/user/dashboard_metadata.rs @@ -13,10 +13,13 @@ pub enum MetaData { ConfiguredRouting(api::ConfiguredRouting), TestPayment(api::TestPayment), IntegrationMethod(api::IntegrationMethod), + ConfigurationType(api::ConfigurationType), IntegrationCompleted(bool), StripeConnected(api::ProcessorConnected), PaypalConnected(api::ProcessorConnected), SPRoutingConfigured(api::ConfiguredRouting), + Feedback(api::Feedback), + ProdIntent(api::ProdIntent), SPTestPayment(bool), DownloadWoocom(bool), ConfigureWoocom(bool), @@ -36,10 +39,13 @@ impl From<&MetaData> for DBEnum { MetaData::ConfiguredRouting(_) => Self::ConfiguredRouting, MetaData::TestPayment(_) => Self::TestPayment, MetaData::IntegrationMethod(_) => Self::IntegrationMethod, + MetaData::ConfigurationType(_) => Self::ConfigurationType, MetaData::IntegrationCompleted(_) => Self::IntegrationCompleted, MetaData::StripeConnected(_) => Self::StripeConnected, MetaData::PaypalConnected(_) => Self::PaypalConnected, MetaData::SPRoutingConfigured(_) => Self::SpRoutingConfigured, + MetaData::Feedback(_) => Self::Feedback, + MetaData::ProdIntent(_) => Self::ProdIntent, MetaData::SPTestPayment(_) => Self::SpTestPayment, MetaData::DownloadWoocom(_) => Self::DownloadWoocom, MetaData::ConfigureWoocom(_) => Self::ConfigureWoocom, diff --git a/crates/router/src/types/fraud_check.rs b/crates/router/src/types/fraud_check.rs new file mode 100644 index 000000000000..4bbba8ac4dca --- /dev/null +++ b/crates/router/src/types/fraud_check.rs @@ -0,0 +1,126 @@ +use crate::{ + connector::signifyd::transformers::{FrmFullfillmentSignifydApiRequest, RefundMethod}, + pii::Serialize, + services, + types::{api, storage_enums, ErrorResponse, ResponseId, RouterData}, +}; +pub type FrmSaleRouterData = RouterData; + +pub type FrmSaleType = + dyn services::ConnectorIntegration; + +#[derive(Debug, Clone)] +pub struct FraudCheckSaleData { + pub amount: i64, + pub order_details: Option>, +} +#[derive(Debug, Clone)] +pub struct FrmRouterData { + pub merchant_id: String, + pub connector: String, + pub payment_id: String, + pub attempt_id: String, + pub request: FrmRequest, + pub response: FrmResponse, +} +#[derive(Debug, Clone)] +pub enum FrmRequest { + Sale(FraudCheckSaleData), + Checkout(FraudCheckCheckoutData), + Transaction(FraudCheckTransactionData), + Fulfillment(FraudCheckFulfillmentData), + RecordReturn(FraudCheckRecordReturnData), +} +#[derive(Debug, Clone)] +pub enum FrmResponse { + Sale(Result), + Checkout(Result), + Transaction(Result), + Fulfillment(Result), + RecordReturn(Result), +} + +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum FraudCheckResponseData { + TransactionResponse { + resource_id: ResponseId, + status: storage_enums::FraudCheckStatus, + connector_metadata: Option, + reason: Option, + score: Option, + }, + FulfillmentResponse { + order_id: String, + shipment_ids: Vec, + }, + RecordReturnResponse { + resource_id: ResponseId, + connector_metadata: Option, + return_id: Option, + }, +} + +pub type FrmCheckoutRouterData = + RouterData; + +pub type FrmCheckoutType = dyn services::ConnectorIntegration< + api::Checkout, + FraudCheckCheckoutData, + FraudCheckResponseData, +>; + +#[derive(Debug, Clone)] +pub struct FraudCheckCheckoutData { + pub amount: i64, + pub order_details: Option>, +} + +pub type FrmTransactionRouterData = + RouterData; + +pub type FrmTransactionType = dyn services::ConnectorIntegration< + api::Transaction, + FraudCheckTransactionData, + FraudCheckResponseData, +>; + +#[derive(Debug, Clone)] +pub struct FraudCheckTransactionData { + pub amount: i64, + pub order_details: Option>, + pub currency: Option, + pub payment_method: Option, +} + +pub type FrmFulfillmentRouterData = + RouterData; + +pub type FrmFulfillmentType = dyn services::ConnectorIntegration< + api::Fulfillment, + FraudCheckFulfillmentData, + FraudCheckResponseData, +>; +pub type FrmRecordReturnRouterData = + RouterData; + +pub type FrmRecordReturnType = dyn services::ConnectorIntegration< + api::RecordReturn, + FraudCheckRecordReturnData, + FraudCheckResponseData, +>; + +#[derive(Debug, Clone)] +pub struct FraudCheckFulfillmentData { + pub amount: i64, + pub order_details: Option>>, + pub fulfillment_request: FrmFullfillmentSignifydApiRequest, +} + +#[derive(Debug, Clone)] +pub struct FraudCheckRecordReturnData { + pub amount: i64, + pub currency: Option, + pub refund_method: RefundMethod, + pub refund_transaction_id: Option, +} diff --git a/crates/router/src/types/pm_auth.rs b/crates/router/src/types/pm_auth.rs new file mode 100644 index 000000000000..e2d08c6afeac --- /dev/null +++ b/crates/router/src/types/pm_auth.rs @@ -0,0 +1,38 @@ +use std::str::FromStr; + +use error_stack::{IntoReport, ResultExt}; +use pm_auth::{ + connector::plaid, + types::{ + self as pm_auth_types, + api::{BoxedPaymentAuthConnector, PaymentAuthConnectorData}, + }, +}; + +use crate::core::{ + errors::{self, ApiErrorResponse}, + pm_auth::helpers::PaymentAuthConnectorDataExt, +}; + +impl PaymentAuthConnectorDataExt for PaymentAuthConnectorData { + fn get_connector_by_name(name: &str) -> errors::CustomResult { + let connector_name = pm_auth_types::PaymentMethodAuthConnectors::from_str(name) + .into_report() + .change_context(ApiErrorResponse::IncorrectConnectorNameGiven) + .attach_printable_lazy(|| { + format!("unable to parse connector: {:?}", name.to_string()) + })?; + let connector = Self::convert_connector(connector_name.clone())?; + Ok(Self { + connector, + connector_name, + }) + } + fn convert_connector( + connector_name: pm_auth_types::PaymentMethodAuthConnectors, + ) -> errors::CustomResult { + match connector_name { + pm_auth_types::PaymentMethodAuthConnectors::Plaid => Ok(Box::new(&plaid::Plaid)), + } + } +} diff --git a/crates/router/src/types/storage.rs b/crates/router/src/types/storage.rs index a83a405f3554..1dc241cde20c 100644 --- a/crates/router/src/types/storage.rs +++ b/crates/router/src/types/storage.rs @@ -1,5 +1,6 @@ pub mod address; pub mod api_keys; +pub mod authorization; pub mod business_profile; pub mod capture; pub mod cards_info; @@ -11,6 +12,7 @@ pub mod enums; pub mod ephemeral_key; pub mod events; pub mod file; +pub mod fraud_check; pub mod gsm; #[cfg(feature = "kv_store")] pub mod kv; @@ -22,30 +24,29 @@ pub mod merchant_key_store; pub mod payment_attempt; pub mod payment_link; pub mod payment_method; -pub mod routing_algorithm; -use std::collections::HashMap; - -pub use diesel_models::{ProcessTracker, ProcessTrackerNew, ProcessTrackerUpdate}; -pub use scheduler::db::process_tracker; -pub mod reverse_lookup; - pub mod payout_attempt; pub mod payouts; mod query; pub mod refund; +pub mod reverse_lookup; +pub mod routing_algorithm; pub mod user; pub mod user_role; +use std::collections::HashMap; + pub use data_models::payments::{ payment_attempt::{PaymentAttempt, PaymentAttemptNew, PaymentAttemptUpdate}, payment_intent::{PaymentIntentNew, PaymentIntentUpdate}, PaymentIntent, }; +pub use diesel_models::{ProcessTracker, ProcessTrackerNew, ProcessTrackerUpdate}; +pub use scheduler::db::process_tracker; pub use self::{ - address::*, api_keys::*, capture::*, cards_info::*, configs::*, customers::*, - dashboard_metadata::*, dispute::*, ephemeral_key::*, events::*, file::*, gsm::*, - locker_mock_up::*, mandate::*, merchant_account::*, merchant_connector_account::*, + address::*, api_keys::*, authorization::*, capture::*, cards_info::*, configs::*, customers::*, + dashboard_metadata::*, dispute::*, ephemeral_key::*, events::*, file::*, fraud_check::*, + gsm::*, locker_mock_up::*, mandate::*, merchant_account::*, merchant_connector_account::*, merchant_key_store::*, payment_link::*, payment_method::*, payout_attempt::*, payouts::*, process_tracker::*, refund::*, reverse_lookup::*, routing_algorithm::*, user::*, user_role::*, }; diff --git a/crates/router/src/types/storage/authorization.rs b/crates/router/src/types/storage/authorization.rs new file mode 100644 index 000000000000..678cd64f8810 --- /dev/null +++ b/crates/router/src/types/storage/authorization.rs @@ -0,0 +1 @@ +pub use diesel_models::authorization::{Authorization, AuthorizationNew, AuthorizationUpdate}; diff --git a/crates/router/src/types/storage/fraud_check.rs b/crates/router/src/types/storage/fraud_check.rs new file mode 100644 index 000000000000..f3dd259c3ce4 --- /dev/null +++ b/crates/router/src/types/storage/fraud_check.rs @@ -0,0 +1,3 @@ +pub use diesel_models::fraud_check::{ + FraudCheck, FraudCheckNew, FraudCheckUpdate, FraudCheckUpdateInternal, +}; diff --git a/crates/router/src/types/storage/refund.rs b/crates/router/src/types/storage/refund.rs index 4d5667700122..bb05233173c8 100644 --- a/crates/router/src/types/storage/refund.rs +++ b/crates/router/src/types/storage/refund.rs @@ -50,23 +50,40 @@ impl RefundDbExt for Refund { .filter(dsl::merchant_id.eq(merchant_id.to_owned())) .order(dsl::modified_at.desc()) .into_boxed(); - - match &refund_list_details.payment_id { - Some(pid) => { - filter = filter.filter(dsl::payment_id.eq(pid.to_owned())); - } - None => { - filter = filter.limit(limit).offset(offset); - } - }; - match &refund_list_details.refund_id { - Some(ref_id) => { - filter = filter.filter(dsl::refund_id.eq(ref_id.to_owned())); - } - None => { - filter = filter.limit(limit).offset(offset); - } + let mut search_by_pay_or_ref_id = false; + + if let (Some(pid), Some(ref_id)) = ( + &refund_list_details.payment_id, + &refund_list_details.refund_id, + ) { + search_by_pay_or_ref_id = true; + filter = filter + .filter(dsl::payment_id.eq(pid.to_owned())) + .or_filter(dsl::refund_id.eq(ref_id.to_owned())) + .limit(limit) + .offset(offset); }; + + if !search_by_pay_or_ref_id { + match &refund_list_details.payment_id { + Some(pid) => { + filter = filter.filter(dsl::payment_id.eq(pid.to_owned())); + } + None => { + filter = filter.limit(limit).offset(offset); + } + }; + } + if !search_by_pay_or_ref_id { + match &refund_list_details.refund_id { + Some(ref_id) => { + filter = filter.filter(dsl::refund_id.eq(ref_id.to_owned())); + } + None => { + filter = filter.limit(limit).offset(offset); + } + }; + } match &refund_list_details.profile_id { Some(profile_id) => { filter = filter @@ -163,7 +180,7 @@ impl RefundDbExt for Refund { let meta = api_models::refunds::RefundListMetaData { connector: filter_connector, currency: filter_currency, - status: filter_status, + refund_status: filter_status, }; Ok(meta) @@ -179,12 +196,28 @@ impl RefundDbExt for Refund { .filter(dsl::merchant_id.eq(merchant_id.to_owned())) .into_boxed(); - if let Some(pay_id) = &refund_list_details.payment_id { - filter = filter.filter(dsl::payment_id.eq(pay_id.to_owned())); + let mut search_by_pay_or_ref_id = false; + + if let (Some(pid), Some(ref_id)) = ( + &refund_list_details.payment_id, + &refund_list_details.refund_id, + ) { + search_by_pay_or_ref_id = true; + filter = filter + .filter(dsl::payment_id.eq(pid.to_owned())) + .or_filter(dsl::refund_id.eq(ref_id.to_owned())); + }; + + if !search_by_pay_or_ref_id { + if let Some(pay_id) = &refund_list_details.payment_id { + filter = filter.filter(dsl::payment_id.eq(pay_id.to_owned())); + } } - if let Some(ref_id) = &refund_list_details.refund_id { - filter = filter.filter(dsl::refund_id.eq(ref_id.to_owned())); + if !search_by_pay_or_ref_id { + if let Some(ref_id) = &refund_list_details.refund_id { + filter = filter.filter(dsl::refund_id.eq(ref_id.to_owned())); + } } if let Some(profile_id) = &refund_list_details.profile_id { filter = filter.filter(dsl::profile_id.eq(profile_id.to_owned())); diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 99096864a000..34ae3dceb5ab 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -685,6 +685,19 @@ impl ForeignFrom for api_models::disputes::DisputeResponse { } } +impl ForeignFrom for payments::IncrementalAuthorizationResponse { + fn foreign_from(authorization: storage::Authorization) -> Self { + Self { + authorization_id: authorization.authorization_id, + amount: authorization.amount, + status: authorization.status, + error_code: authorization.error_code, + error_message: authorization.error_message, + previously_authorized_amount: authorization.previously_authorized_amount, + } + } +} + impl ForeignFrom for api_models::disputes::DisputeResponsePaymentsRetrieve { fn foreign_from(dispute: storage::Dispute) -> Self { Self { @@ -747,7 +760,7 @@ impl TryFrom for api_models::admin::MerchantCo .parse_value("FrmConfigs") .change_context(errors::ApiErrorResponse::InvalidDataFormat { field_name: "frm_configs".to_string(), - expected_format: "[{ \"gateway\": \"stripe\", \"payment_methods\": [{ \"payment_method\": \"card\",\"payment_method_types\": [{\"payment_method_type\": \"credit\",\"card_networks\": [\"Visa\"],\"flow\": \"pre\",\"action\": \"cancel_txn\"}]}]}]".to_string(), + expected_format: r#"[{ "gateway": "stripe", "payment_methods": [{ "payment_method": "card","payment_method_types": [{"payment_method_type": "credit","card_networks": ["Visa"],"flow": "pre","action": "cancel_txn"}]}]}]"#.to_string(), }) }) .collect::, _>>()?; diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index f1590342e17c..42116e1ecbf0 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "olap")] +pub mod connector_onboarding; pub mod currency; pub mod custom_serde; pub mod db_utils; diff --git a/crates/router/src/utils/connector_onboarding.rs b/crates/router/src/utils/connector_onboarding.rs new file mode 100644 index 000000000000..e8afcd68a468 --- /dev/null +++ b/crates/router/src/utils/connector_onboarding.rs @@ -0,0 +1,36 @@ +use crate::{ + core::errors::{api_error_response::NotImplementedMessage, ApiErrorResponse, RouterResult}, + routes::app::settings, + types::{self, api::enums}, +}; + +pub mod paypal; + +pub fn get_connector_auth( + connector: enums::Connector, + connector_data: &settings::ConnectorOnboarding, +) -> RouterResult { + match connector { + enums::Connector::Paypal => Ok(types::ConnectorAuthType::BodyKey { + api_key: connector_data.paypal.client_secret.clone(), + key1: connector_data.paypal.client_id.clone(), + }), + _ => Err(ApiErrorResponse::NotImplemented { + message: NotImplementedMessage::Reason(format!( + "Onboarding is not implemented for {}", + connector + )), + } + .into()), + } +} + +pub fn is_enabled( + connector: types::Connector, + conf: &settings::ConnectorOnboarding, +) -> Option { + match connector { + enums::Connector::Paypal => Some(conf.paypal.enabled), + _ => None, + } +} diff --git a/crates/router/src/utils/connector_onboarding/paypal.rs b/crates/router/src/utils/connector_onboarding/paypal.rs new file mode 100644 index 000000000000..6d7f2692be72 --- /dev/null +++ b/crates/router/src/utils/connector_onboarding/paypal.rs @@ -0,0 +1,78 @@ +use common_utils::request::{Method, Request, RequestBuilder, RequestContent}; +use error_stack::{IntoReport, ResultExt}; +use http::header; + +use crate::{ + connector, + core::errors::{ApiErrorResponse, RouterResult}, + routes::AppState, + types, + types::api::{ + enums, + verify_connector::{self as verify_connector_types, VerifyConnector}, + }, + utils::verify_connector as verify_connector_utils, +}; + +pub async fn generate_access_token(state: AppState) -> RouterResult { + let connector = enums::Connector::Paypal; + let boxed_connector = types::api::ConnectorData::convert_connector( + &state.conf.connectors, + connector.to_string().as_str(), + )?; + let connector_auth = super::get_connector_auth(connector, &state.conf.connector_onboarding)?; + + connector::Paypal::get_access_token( + &state, + verify_connector_types::VerifyConnectorData { + connector: *boxed_connector, + connector_auth, + card_details: verify_connector_utils::get_test_card_details(connector)? + .ok_or(ApiErrorResponse::FlowNotSupported { + flow: "Connector onboarding".to_string(), + connector: connector.to_string(), + }) + .into_report()?, + }, + ) + .await? + .ok_or(ApiErrorResponse::InternalServerError) + .into_report() + .attach_printable("Error occurred while retrieving access token") +} + +pub fn build_paypal_post_request( + url: String, + body: T, + access_token: String, +) -> RouterResult +where + T: serde::Serialize + Send + 'static, +{ + Ok(RequestBuilder::new() + .method(Method::Post) + .url(&url) + .attach_default_headers() + .header( + header::AUTHORIZATION.to_string().as_str(), + format!("Bearer {}", access_token).as_str(), + ) + .header( + header::CONTENT_TYPE.to_string().as_str(), + "application/json", + ) + .set_body(RequestContent::Json(Box::new(body))) + .build()) +} + +pub fn build_paypal_get_request(url: String, access_token: String) -> RouterResult { + Ok(RequestBuilder::new() + .method(Method::Get) + .url(&url) + .attach_default_headers() + .header( + header::AUTHORIZATION.to_string().as_str(), + format!("Bearer {}", access_token).as_str(), + ) + .build()) +} diff --git a/crates/router/src/utils/db_utils.rs b/crates/router/src/utils/db_utils.rs index febc226c0202..219b6f9777f9 100644 --- a/crates/router/src/utils/db_utils.rs +++ b/crates/router/src/utils/db_utils.rs @@ -1,4 +1,7 @@ -use crate::{core::errors, routes::metrics}; +use crate::{ + core::errors::{self, utils::RedisErrorExt}, + routes::metrics, +}; /// Generates hscan field pattern. Suppose the field is pa_1234_ref_1211 it will generate /// pa_1234_ref_* @@ -28,7 +31,8 @@ where metrics::KV_MISS.add(&metrics::CONTEXT, 1, &[]); database_call_closure().await } - _ => Err(redis_error.change_context(errors::StorageError::KVError)), + // Keeping the key empty here since the error would never go here. + _ => Err(redis_error.to_redis_failed_response("")), }, } } diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs index 696aa4090044..5f765028014f 100644 --- a/crates/router/src/utils/user.rs +++ b/crates/router/src/utils/user.rs @@ -1,11 +1,13 @@ -use diesel_models::enums::UserStatus; +use api_models::user as user_api; +use diesel_models::{enums::UserStatus, user_role::UserRole}; use error_stack::ResultExt; +use masking::Secret; use crate::{ core::errors::{UserErrors, UserResult}, routes::AppState, - services::authentication::UserFromToken, - types::domain::MerchantAccount, + services::authentication::{AuthToken, UserFromToken}, + types::domain::{MerchantAccount, UserFromStorage}, }; pub mod dashboard_metadata; @@ -68,3 +70,59 @@ pub async fn get_merchant_ids_for_user(state: AppState, user_id: &str) -> UserRe }) .collect()) } + +pub async fn generate_jwt_auth_token( + state: AppState, + user: &UserFromStorage, + user_role: &UserRole, +) -> UserResult> { + let token = AuthToken::new_token( + user.get_user_id().to_string(), + user_role.merchant_id.clone(), + user_role.role_id.clone(), + &state.conf, + user_role.org_id.clone(), + ) + .await?; + Ok(Secret::new(token)) +} + +pub async fn generate_jwt_auth_token_with_custom_merchant_id( + state: AppState, + user: &UserFromStorage, + user_role: &UserRole, + merchant_id: String, +) -> UserResult> { + let token = AuthToken::new_token( + user.get_user_id().to_string(), + merchant_id, + user_role.role_id.clone(), + &state.conf, + user_role.org_id.to_owned(), + ) + .await?; + Ok(Secret::new(token)) +} + +#[allow(unused_variables)] +pub fn get_dashboard_entry_response( + state: AppState, + user: UserFromStorage, + user_role: UserRole, + token: Secret, +) -> UserResult { + #[cfg(feature = "email")] + let verification_days_left = user.get_verification_days_left(state)?; + #[cfg(not(feature = "email"))] + let verification_days_left = None; + + Ok(user_api::DashboardEntryResponse { + merchant_id: user_role.merchant_id, + token, + name: user.get_name(), + email: user.get_email(), + user_id: user.get_user_id().to_string(), + verification_days_left, + user_role: user_role.role_id, + }) +} diff --git a/crates/router/src/utils/user/dashboard_metadata.rs b/crates/router/src/utils/user/dashboard_metadata.rs index 5f354e613f95..40594a6e49f6 100644 --- a/crates/router/src/utils/user/dashboard_metadata.rs +++ b/crates/router/src/utils/user/dashboard_metadata.rs @@ -6,7 +6,7 @@ use api_models::user::dashboard_metadata::{ }; use diesel_models::{ enums::DashboardMetadata as DBEnum, - user::dashboard_metadata::{DashboardMetadata, DashboardMetadataNew}, + user::dashboard_metadata::{DashboardMetadata, DashboardMetadataNew, DashboardMetadataUpdate}, }; use error_stack::{IntoReport, ResultExt}; use masking::Secret; @@ -50,6 +50,40 @@ pub async fn insert_merchant_scoped_metadata_to_db( e.change_context(UserErrors::InternalServerError) }) } +pub async fn insert_user_scoped_metadata_to_db( + state: &AppState, + user_id: String, + merchant_id: String, + org_id: String, + metadata_key: DBEnum, + metadata_value: impl serde::Serialize, +) -> UserResult { + let now = common_utils::date_time::now(); + let data_value = serde_json::to_value(metadata_value) + .into_report() + .change_context(UserErrors::InternalServerError) + .attach_printable("Error Converting Struct To Serde Value")?; + state + .store + .insert_metadata(DashboardMetadataNew { + user_id: Some(user_id.clone()), + merchant_id, + org_id, + data_key: metadata_key, + data_value, + created_by: user_id.clone(), + created_at: now, + last_modified_by: user_id, + last_modified_at: now, + }) + .await + .map_err(|e| { + if e.current_context().is_db_unique_violation() { + return e.change_context(UserErrors::MetadataAlreadySet); + } + e.change_context(UserErrors::InternalServerError) + }) +} pub async fn get_merchant_scoped_metadata_from_db( state: &AppState, @@ -73,6 +107,88 @@ pub async fn get_merchant_scoped_metadata_from_db( } } } +pub async fn get_user_scoped_metadata_from_db( + state: &AppState, + user_id: String, + merchant_id: String, + org_id: String, + metadata_keys: Vec, +) -> UserResult> { + match state + .store + .find_user_scoped_dashboard_metadata(&user_id, &merchant_id, &org_id, metadata_keys) + .await + { + Ok(data) => Ok(data), + Err(e) => { + if e.current_context().is_db_not_found() { + return Ok(Vec::with_capacity(0)); + } + Err(e + .change_context(UserErrors::InternalServerError) + .attach_printable("DB Error Fetching DashboardMetaData")) + } + } +} + +pub async fn update_merchant_scoped_metadata( + state: &AppState, + user_id: String, + merchant_id: String, + org_id: String, + metadata_key: DBEnum, + metadata_value: impl serde::Serialize, +) -> UserResult { + let data_value = serde_json::to_value(metadata_value) + .into_report() + .change_context(UserErrors::InternalServerError) + .attach_printable("Error Converting Struct To Serde Value")?; + + state + .store + .update_metadata( + None, + merchant_id, + org_id, + metadata_key, + DashboardMetadataUpdate::UpdateData { + data_key: metadata_key, + data_value, + last_modified_by: user_id, + }, + ) + .await + .change_context(UserErrors::InternalServerError) +} +pub async fn update_user_scoped_metadata( + state: &AppState, + user_id: String, + merchant_id: String, + org_id: String, + metadata_key: DBEnum, + metadata_value: impl serde::Serialize, +) -> UserResult { + let data_value = serde_json::to_value(metadata_value) + .into_report() + .change_context(UserErrors::InternalServerError) + .attach_printable("Error Converting Struct To Serde Value")?; + + state + .store + .update_metadata( + Some(user_id.clone()), + merchant_id, + org_id, + metadata_key, + DashboardMetadataUpdate::UpdateData { + data_key: metadata_key, + data_value, + last_modified_by: user_id, + }, + ) + .await + .change_context(UserErrors::InternalServerError) +} pub fn deserialize_to_response(data: Option<&DashboardMetadata>) -> UserResult> where @@ -87,7 +203,7 @@ where pub fn separate_metadata_type_based_on_scope( metadata_keys: Vec, ) -> (Vec, Vec) { - let (mut merchant_scoped, user_scoped) = ( + let (mut merchant_scoped, mut user_scoped) = ( Vec::with_capacity(metadata_keys.len()), Vec::with_capacity(metadata_keys.len()), ); @@ -102,6 +218,7 @@ pub fn separate_metadata_type_based_on_scope( | DBEnum::ConfiguredRouting | DBEnum::TestPayment | DBEnum::IntegrationMethod + | DBEnum::ConfigurationType | DBEnum::IntegrationCompleted | DBEnum::StripeConnected | DBEnum::PaypalConnected @@ -111,11 +228,19 @@ pub fn separate_metadata_type_based_on_scope( | DBEnum::ConfigureWoocom | DBEnum::SetupWoocomWebhook | DBEnum::IsMultipleConfiguration => merchant_scoped.push(key), + DBEnum::Feedback | DBEnum::ProdIntent => user_scoped.push(key), } } (merchant_scoped, user_scoped) } +pub fn is_update_required(metadata: &UserResult) -> bool { + match metadata { + Ok(_) => false, + Err(e) => matches!(e.current_context(), UserErrors::MetadataAlreadySet), + } +} + pub fn is_backfill_required(metadata_key: &DBEnum) -> bool { matches!( metadata_key, diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index 7a9cf6d2b7db..9f95e2d078dd 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -34,7 +34,7 @@ pub async fn generate_sample_data( &state.store.get_master_key().to_vec().into(), ) .await - .change_context(SampleDataError::DatabaseError)?; + .change_context(SampleDataError::InternalServerError)?; let merchant_from_db = state .store @@ -196,6 +196,7 @@ pub async fn generate_sample_data( surcharge_applicable: Default::default(), request_incremental_authorization: Default::default(), incremental_authorization_allowed: Default::default(), + authorization_count: Default::default(), }; let payment_attempt = PaymentAttemptBatchNew { attempt_id: attempt_id.clone(), diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index 823b3eae497d..7e5cfeb43974 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -542,6 +542,7 @@ pub trait ConnectorActions: Connector { Ok(types::PaymentsResponseData::PreProcessingResponse { .. }) => None, Ok(types::PaymentsResponseData::ThreeDSEnrollmentResponse { .. }) => None, Ok(types::PaymentsResponseData::MultipleCaptureResponse { .. }) => None, + Ok(types::PaymentsResponseData::IncrementalAuthorizationResponse { .. }) => None, Err(_) => None, } } @@ -1029,6 +1030,7 @@ pub fn get_connector_transaction_id( Ok(types::PaymentsResponseData::ConnectorCustomerResponse { .. }) => None, Ok(types::PaymentsResponseData::ThreeDSEnrollmentResponse { .. }) => None, Ok(types::PaymentsResponseData::MultipleCaptureResponse { .. }) => None, + Ok(types::PaymentsResponseData::IncrementalAuthorizationResponse { .. }) => None, Err(_) => None, } } diff --git a/crates/router_derive/src/macros/operation.rs b/crates/router_derive/src/macros/operation.rs index 370e03b984ba..e743a2d9cc52 100644 --- a/crates/router_derive/src/macros/operation.rs +++ b/crates/router_derive/src/macros/operation.rs @@ -27,6 +27,8 @@ pub enum Derives { Verify, Session, SessionData, + IncrementalAuthorization, + IncrementalAuthorizationData, } impl Derives { @@ -95,6 +97,12 @@ impl Conversion { } Derives::Session => syn::Ident::new("PaymentsSessionRequest", Span::call_site()), Derives::SessionData => syn::Ident::new("PaymentsSessionData", Span::call_site()), + Derives::IncrementalAuthorization => { + syn::Ident::new("PaymentsIncrementalAuthorizationRequest", Span::call_site()) + } + Derives::IncrementalAuthorizationData => { + syn::Ident::new("PaymentsIncrementalAuthorizationData", Span::call_site()) + } } } @@ -414,6 +422,7 @@ pub fn operation_derive_inner(input: DeriveInput) -> syn::Result syn::Result = once_cell::sync::Lazy::new(|| $meter.u64_histogram($description).init()); }; } + +/// Create a [`Histogram`][Histogram] i64 metric with the specified name and an optional description, +/// associated with the specified meter. Note that the meter must be to a valid [`Meter`][Meter]. +/// +/// [Histogram]: opentelemetry::metrics::Histogram +/// [Meter]: opentelemetry::metrics::Meter +#[macro_export] +macro_rules! histogram_metric_i64 { + ($name:ident, $meter:ident) => { + pub(crate) static $name: once_cell::sync::Lazy< + $crate::opentelemetry::metrics::Histogram, + > = once_cell::sync::Lazy::new(|| $meter.i64_histogram(stringify!($name)).init()); + }; + ($name:ident, $meter:ident, $description:literal) => { + pub(crate) static $name: once_cell::sync::Lazy< + $crate::opentelemetry::metrics::Histogram, + > = once_cell::sync::Lazy::new(|| $meter.i64_histogram($description).init()); + }; +} diff --git a/crates/storage_impl/src/errors.rs b/crates/storage_impl/src/errors.rs index bc68986cb8ea..f0cbebf78c55 100644 --- a/crates/storage_impl/src/errors.rs +++ b/crates/storage_impl/src/errors.rs @@ -92,15 +92,7 @@ impl Into for &StorageError { key: None, } } - storage_errors::DatabaseError::NoFieldsToUpdate => { - DataStorageError::DatabaseError("No fields to update".to_string()) - } - storage_errors::DatabaseError::QueryGenerationFailed => { - DataStorageError::DatabaseError("Query generation failed".to_string()) - } - storage_errors::DatabaseError::Others => { - DataStorageError::DatabaseError("Unknown database error".to_string()) - } + err => DataStorageError::DatabaseError(error_stack::report!(*err)), }, StorageError::ValueNotFound(i) => DataStorageError::ValueNotFound(i.clone()), StorageError::DuplicateValue { entity, key } => DataStorageError::DuplicateValue { @@ -158,6 +150,26 @@ impl StorageError { } } +pub trait RedisErrorExt { + #[track_caller] + fn to_redis_failed_response(self, key: &str) -> error_stack::Report; +} + +impl RedisErrorExt for error_stack::Report { + fn to_redis_failed_response(self, key: &str) -> error_stack::Report { + match self.current_context() { + RedisError::NotFound => self.change_context(DataStorageError::ValueNotFound(format!( + "Data does not exist for key {key}", + ))), + RedisError::SetNxFailed => self.change_context(DataStorageError::DuplicateValue { + entity: "redis", + key: Some(key.to_string()), + }), + _ => self.change_context(DataStorageError::KVError), + } + } +} + impl_error_type!(EncryptionError, "Encryption error"); #[derive(Debug, thiserror::Error)] diff --git a/crates/storage_impl/src/lib.rs b/crates/storage_impl/src/lib.rs index dc0dea4bb59c..7e2c7f2fc3c5 100644 --- a/crates/storage_impl/src/lib.rs +++ b/crates/storage_impl/src/lib.rs @@ -251,14 +251,6 @@ pub(crate) fn diesel_error_to_data_error( entity: "entity ", key: None, }, - diesel_models::errors::DatabaseError::NoFieldsToUpdate => { - StorageError::DatabaseError("No fields to update".to_string()) - } - diesel_models::errors::DatabaseError::QueryGenerationFailed => { - StorageError::DatabaseError("Query generation failed".to_string()) - } - diesel_models::errors::DatabaseError::Others => { - StorageError::DatabaseError("Others".to_string()) - } + _ => StorageError::DatabaseError(error_stack::report!(*diesel_error)), } } diff --git a/crates/storage_impl/src/lookup.rs b/crates/storage_impl/src/lookup.rs index bd045fedd379..c96e24515772 100644 --- a/crates/storage_impl/src/lookup.rs +++ b/crates/storage_impl/src/lookup.rs @@ -11,6 +11,7 @@ use redis_interface::SetnxReply; use crate::{ diesel_error_to_data_error, + errors::RedisErrorExt, redis::kv_store::{kv_wrapper, KvOperation}, utils::{self, try_redis_get_else_try_database_get}, DatabaseStore, KVRouterStore, RouterStore, @@ -97,7 +98,7 @@ impl ReverseLookupInterface for KVRouterStore { format!("reverse_lookup_{}", &created_rev_lookup.lookup_id), ) .await - .change_context(errors::StorageError::KVError)? + .map_err(|err| err.to_redis_failed_response(&created_rev_lookup.lookup_id))? .try_into_setnx() { Ok(SetnxReply::KeySet) => Ok(created_rev_lookup), diff --git a/crates/storage_impl/src/mock_db/payment_intent.rs b/crates/storage_impl/src/mock_db/payment_intent.rs index a3e82c1d1044..1da3df0bdef2 100644 --- a/crates/storage_impl/src/mock_db/payment_intent.rs +++ b/crates/storage_impl/src/mock_db/payment_intent.rs @@ -108,6 +108,7 @@ impl PaymentIntentInterface for MockDb { surcharge_applicable: new.surcharge_applicable, request_incremental_authorization: new.request_incremental_authorization, incremental_authorization_allowed: new.incremental_authorization_allowed, + authorization_count: new.authorization_count, }; payment_intents.push(payment_intent.clone()); Ok(payment_intent) diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index e86119e41af6..425cdd216fec 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -1,5 +1,5 @@ use api_models::enums::{AuthenticationType, Connector, PaymentMethod, PaymentMethodType}; -use common_utils::errors::CustomResult; +use common_utils::{errors::CustomResult, fallback_reverse_lookup_not_found}; use data_models::{ errors, mandates::{MandateAmountData, MandateDataType}, @@ -29,6 +29,7 @@ use router_env::{instrument, tracing}; use crate::{ diesel_error_to_data_error, + errors::RedisErrorExt, lookup::ReverseLookupInterface, redis::kv_store::{kv_wrapper, KvOperation}, utils::{pg_connection_read, pg_connection_write, try_redis_get_else_try_database_get}, @@ -399,6 +400,20 @@ impl PaymentAttemptInterface for KVRouterStore { }, }; + //Reverse lookup for attempt_id + let reverse_lookup = ReverseLookupNew { + lookup_id: format!( + "pa_{}_{}", + &created_attempt.merchant_id, &created_attempt.attempt_id, + ), + pk_id: key.clone(), + sk_id: field.clone(), + source: "payment_attempt".to_string(), + updated_by: storage_scheme.to_string(), + }; + self.insert_reverse_lookup(reverse_lookup, storage_scheme) + .await?; + match kv_wrapper::( self, KvOperation::HSetNx( @@ -409,7 +424,7 @@ impl PaymentAttemptInterface for KVRouterStore { &key, ) .await - .change_context(errors::StorageError::KVError)? + .map_err(|err| err.to_redis_failed_response(&key))? .try_into_hsetnx() { Ok(HsetnxReply::KeyNotSet) => Err(errors::StorageError::DuplicateValue { @@ -417,23 +432,7 @@ impl PaymentAttemptInterface for KVRouterStore { key: Some(key), }) .into_report(), - Ok(HsetnxReply::KeySet) => { - //Reverse lookup for attempt_id - let reverse_lookup = ReverseLookupNew { - lookup_id: format!( - "{}_{}", - &created_attempt.merchant_id, &created_attempt.attempt_id, - ), - pk_id: key, - sk_id: field, - source: "payment_attempt".to_string(), - updated_by: storage_scheme.to_string(), - }; - self.insert_reverse_lookup(reverse_lookup, storage_scheme) - .await?; - - Ok(created_attempt) - } + Ok(HsetnxReply::KeySet) => Ok(created_attempt), Err(error) => Err(error.change_context(errors::StorageError::KVError)), } } @@ -480,16 +479,6 @@ impl PaymentAttemptInterface for KVRouterStore { }, }; - kv_wrapper::<(), _, _>( - self, - KvOperation::Hset::((&field, redis_value), redis_entry), - &key, - ) - .await - .change_context(errors::StorageError::KVError)? - .try_into_hset() - .change_context(errors::StorageError::KVError)?; - match ( old_connector_transaction_id, &updated_attempt.connector_transaction_id, @@ -549,6 +538,16 @@ impl PaymentAttemptInterface for KVRouterStore { (_, _) => {} } + kv_wrapper::<(), _, _>( + self, + KvOperation::Hset::((&field, redis_value), redis_entry), + &key, + ) + .await + .change_context(errors::StorageError::KVError)? + .try_into_hset() + .change_context(errors::StorageError::KVError)?; + Ok(updated_attempt) } } @@ -574,10 +573,20 @@ impl PaymentAttemptInterface for KVRouterStore { } MerchantStorageScheme::RedisKv => { // We assume that PaymentAttempt <=> PaymentIntent is a one-to-one relation for now - let lookup_id = format!("conn_trans_{merchant_id}_{connector_transaction_id}"); - let lookup = self - .get_lookup_by_lookup_id(&lookup_id, storage_scheme) - .await?; + let lookup_id = format!("pa_conn_trans_{merchant_id}_{connector_transaction_id}"); + let lookup = fallback_reverse_lookup_not_found!( + self.get_lookup_by_lookup_id(&lookup_id, storage_scheme) + .await, + self.router_store + .find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( + connector_transaction_id, + payment_id, + merchant_id, + storage_scheme, + ) + .await + ); + let key = &lookup.pk_id; Box::pin(try_redis_get_else_try_database_get( @@ -707,10 +716,18 @@ impl PaymentAttemptInterface for KVRouterStore { .await } MerchantStorageScheme::RedisKv => { - let lookup_id = format!("{merchant_id}_{connector_txn_id}"); - let lookup = self - .get_lookup_by_lookup_id(&lookup_id, storage_scheme) - .await?; + let lookup_id = format!("pa_conn_trans_{merchant_id}_{connector_txn_id}"); + let lookup = fallback_reverse_lookup_not_found!( + self.get_lookup_by_lookup_id(&lookup_id, storage_scheme) + .await, + self.router_store + .find_payment_attempt_by_merchant_id_connector_txn_id( + merchant_id, + connector_txn_id, + storage_scheme, + ) + .await + ); let key = &lookup.pk_id; Box::pin(try_redis_get_else_try_database_get( @@ -799,10 +816,19 @@ impl PaymentAttemptInterface for KVRouterStore { .await } MerchantStorageScheme::RedisKv => { - let lookup_id = format!("{merchant_id}_{attempt_id}"); - let lookup = self - .get_lookup_by_lookup_id(&lookup_id, storage_scheme) - .await?; + let lookup_id = format!("pa_{merchant_id}_{attempt_id}"); + let lookup = fallback_reverse_lookup_not_found!( + self.get_lookup_by_lookup_id(&lookup_id, storage_scheme) + .await, + self.router_store + .find_payment_attempt_by_attempt_id_merchant_id( + attempt_id, + merchant_id, + storage_scheme, + ) + .await + ); + let key = &lookup.pk_id; Box::pin(try_redis_get_else_try_database_get( async { @@ -846,10 +872,18 @@ impl PaymentAttemptInterface for KVRouterStore { .await } MerchantStorageScheme::RedisKv => { - let lookup_id = format!("preprocessing_{merchant_id}_{preprocessing_id}"); - let lookup = self - .get_lookup_by_lookup_id(&lookup_id, storage_scheme) - .await?; + let lookup_id = format!("pa_preprocessing_{merchant_id}_{preprocessing_id}"); + let lookup = fallback_reverse_lookup_not_found!( + self.get_lookup_by_lookup_id(&lookup_id, storage_scheme) + .await, + self.router_store + .find_payment_attempt_by_preprocessing_id_merchant_id( + preprocessing_id, + merchant_id, + storage_scheme, + ) + .await + ); let key = &lookup.pk_id; Box::pin(try_redis_get_else_try_database_get( @@ -1467,6 +1501,13 @@ impl DataModelExt for PaymentAttemptUpdate { connector, updated_by, }, + Self::IncrementalAuthorizationAmountUpdate { + amount, + amount_capturable, + } => DieselPaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { + amount, + amount_capturable, + }, } } @@ -1728,6 +1769,13 @@ impl DataModelExt for PaymentAttemptUpdate { connector, updated_by, }, + DieselPaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { + amount, + amount_capturable, + } => Self::IncrementalAuthorizationAmountUpdate { + amount, + amount_capturable, + }, } } } @@ -1743,7 +1791,7 @@ async fn add_connector_txn_id_to_reverse_lookup( ) -> CustomResult { let field = format!("pa_{}", updated_attempt_attempt_id); let reverse_lookup_new = ReverseLookupNew { - lookup_id: format!("conn_trans_{}_{}", merchant_id, connector_transaction_id), + lookup_id: format!("pa_conn_trans_{}_{}", merchant_id, connector_transaction_id), pk_id: key.to_owned(), sk_id: field.clone(), source: "payment_attempt".to_string(), @@ -1765,7 +1813,7 @@ async fn add_preprocessing_id_to_reverse_lookup( ) -> CustomResult { let field = format!("pa_{}", updated_attempt_attempt_id); let reverse_lookup_new = ReverseLookupNew { - lookup_id: format!("preprocessing_{}_{}", merchant_id, preprocessing_id), + lookup_id: format!("pa_preprocessing_{}_{}", merchant_id, preprocessing_id), pk_id: key.to_owned(), sk_id: field.clone(), source: "payment_attempt".to_string(), diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index fdf9875bc1ff..61229ca890c3 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -38,6 +38,7 @@ use router_env::{instrument, tracing}; use crate::connection; use crate::{ diesel_error_to_data_error, + errors::RedisErrorExt, redis::kv_store::{kv_wrapper, KvOperation}, utils::{self, pg_connection_read, pg_connection_write}, DataModelExt, DatabaseStore, KVRouterStore, @@ -99,6 +100,7 @@ impl PaymentIntentInterface for KVRouterStore { surcharge_applicable: new.surcharge_applicable, request_incremental_authorization: new.request_incremental_authorization, incremental_authorization_allowed: new.incremental_authorization_allowed, + authorization_count: new.authorization_count, }; let redis_entry = kv::TypedSql { op: kv::DBOperation::Insert { @@ -116,7 +118,7 @@ impl PaymentIntentInterface for KVRouterStore { &key, ) .await - .change_context(StorageError::KVError)? + .map_err(|err| err.to_redis_failed_response(&key))? .try_into_hsetnx() { Ok(HsetnxReply::KeyNotSet) => Err(StorageError::DuplicateValue { @@ -177,7 +179,7 @@ impl PaymentIntentInterface for KVRouterStore { &key, ) .await - .change_context(StorageError::KVError)? + .map_err(|err| err.to_redis_failed_response(&key))? .try_into_hset() .change_context(StorageError::KVError)?; @@ -493,12 +495,13 @@ impl PaymentIntentInterface for crate::RouterStore { .map(PaymentIntent::from_storage_model) .collect::>() }) - .into_report() .map_err(|er| { - let new_err = StorageError::DatabaseError(format!("{er:?}")); - er.change_context(new_err) + StorageError::DatabaseError( + error_stack::report!(diesel_models::errors::DatabaseError::from(er)) + .attach_printable("Error filtering payment records"), + ) }) - .attach_printable_lazy(|| "Error filtering records by predicate") + .into_report() } #[cfg(feature = "olap")] @@ -645,12 +648,13 @@ impl PaymentIntentInterface for crate::RouterStore { }) .collect() }) - .into_report() .map_err(|er| { - let new_er = StorageError::DatabaseError(format!("{er:?}")); - er.change_context(new_er) + StorageError::DatabaseError( + error_stack::report!(diesel_models::errors::DatabaseError::from(er)) + .attach_printable("Error filtering payment records"), + ) }) - .attach_printable("Error filtering payment records") + .into_report() } #[cfg(feature = "olap")] @@ -711,12 +715,13 @@ impl PaymentIntentInterface for crate::RouterStore { db_metrics::DatabaseOperation::Filter, ) .await - .into_report() .map_err(|er| { - let new_err = StorageError::DatabaseError(format!("{er:?}")); - er.change_context(new_err) + StorageError::DatabaseError( + error_stack::report!(diesel_models::errors::DatabaseError::from(er)) + .attach_printable("Error filtering payment records"), + ) }) - .attach_printable_lazy(|| "Error filtering records by predicate") + .into_report() } } @@ -762,6 +767,7 @@ impl DataModelExt for PaymentIntentNew { surcharge_applicable: self.surcharge_applicable, request_incremental_authorization: self.request_incremental_authorization, incremental_authorization_allowed: self.incremental_authorization_allowed, + authorization_count: self.authorization_count, } } @@ -804,6 +810,7 @@ impl DataModelExt for PaymentIntentNew { surcharge_applicable: storage_model.surcharge_applicable, request_incremental_authorization: storage_model.request_incremental_authorization, incremental_authorization_allowed: storage_model.incremental_authorization_allowed, + authorization_count: storage_model.authorization_count, } } } @@ -851,6 +858,7 @@ impl DataModelExt for PaymentIntent { surcharge_applicable: self.surcharge_applicable, request_incremental_authorization: self.request_incremental_authorization, incremental_authorization_allowed: self.incremental_authorization_allowed, + authorization_count: self.authorization_count, } } @@ -894,6 +902,7 @@ impl DataModelExt for PaymentIntent { surcharge_applicable: storage_model.surcharge_applicable, request_incremental_authorization: storage_model.request_incremental_authorization, incremental_authorization_allowed: storage_model.incremental_authorization_allowed, + authorization_count: storage_model.authorization_count, } } } @@ -1038,6 +1047,14 @@ impl DataModelExt for PaymentIntentUpdate { surcharge_applicable: Some(surcharge_applicable), updated_by, }, + Self::IncrementalAuthorizationAmountUpdate { amount } => { + DieselPaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } + } + Self::AuthorizationCountUpdate { + authorization_count, + } => DieselPaymentIntentUpdate::AuthorizationCountUpdate { + authorization_count, + }, } } diff --git a/crates/storage_impl/src/utils.rs b/crates/storage_impl/src/utils.rs index 6d6e1cd5402b..6d69f02593fd 100644 --- a/crates/storage_impl/src/utils.rs +++ b/crates/storage_impl/src/utils.rs @@ -3,7 +3,7 @@ use data_models::errors::StorageError; use diesel::PgConnection; use error_stack::{IntoReport, ResultExt}; -use crate::{metrics, DatabaseStore}; +use crate::{errors::RedisErrorExt, metrics, DatabaseStore}; pub async fn pg_connection_read( store: &T, @@ -64,7 +64,8 @@ where metrics::KV_MISS.add(&metrics::CONTEXT, 1, &[]); database_call_closure().await } - _ => Err(redis_error.change_context(StorageError::KVError)), + // Keeping the key empty here since the error would never go here. + _ => Err(redis_error.to_redis_failed_response("")), }, } } diff --git a/crates/test_utils/README.md b/crates/test_utils/README.md index 2edbc7104c25..a82c74cb59f6 100644 --- a/crates/test_utils/README.md +++ b/crates/test_utils/README.md @@ -22,9 +22,9 @@ The heart of `newman`(with directory support) and `UI-tests` Required fields: -- `--admin_api_key` -- Admin API Key of the environment. `test_admin` is the Admin API Key for running locally -- `--base_url` -- Base URL of the environment. `http://127.0.0.1:8080` / `http://localhost:8080` is the Base URL for running locally -- `--connector_name` -- Name of the connector that you wish to run. Example: `adyen`, `shift4`, `stripe` +- `--admin-api-key` -- Admin API Key of the environment. `test_admin` is the Admin API Key for running locally +- `--base-url` -- Base URL of the environment. `http://127.0.0.1:8080` / `http://localhost:8080` is the Base URL for running locally +- `--connector-name` -- Name of the connector that you wish to run. Example: `adyen`, `shift4`, `stripe` Optional fields: @@ -46,7 +46,7 @@ Optional fields: - Tests can be run with the following command: ```shell - cargo run --package test_utils --bin test_utils -- --connector_name= --base_url= --admin_api_key= \ + cargo run --package test_utils --bin test_utils -- --connector-name= --base-url= --admin-api-key= \ # optionally --folder ",,..." --verbose ``` diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index bec1074b99d0..f4070beb943d 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -115,6 +115,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" shift4.base_url = "https://api.shift4.com/" +signifyd.base_url = "https://api.signifyd.com/" square.base_url = "https://connect.squareupsandbox.com/" square.secondary_base_url = "https://pci-connect.squareupsandbox.com/" stax.base_url = "https://apiprod.fattlabs.com/" @@ -242,8 +243,8 @@ pay_later.klarna = {connector_list = "adyen"} wallet.google_pay = {connector_list = "stripe,adyen"} wallet.apple_pay = {connector_list = "stripe,adyen"} wallet.paypal = {connector_list = "adyen"} -card.credit = {connector_list = "stripe,adyen,authorizedotnet,globalpay,worldpay,multisafepay,nmi,nexinets,noon"} -card.debit = {connector_list = "stripe,adyen,authorizedotnet,globalpay,worldpay,multisafepay,nmi,nexinets,noon"} +card.credit = {connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon"} +card.debit = {connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon"} bank_debit.ach = { connector_list = "gocardless"} bank_debit.becs = { connector_list = "gocardless"} bank_debit.sepa = { connector_list = "gocardless"} @@ -262,3 +263,11 @@ connection_timeout = 10 [kv_config] ttl = 300 # 5 * 60 seconds + +[frm] +enabled = true + +[connector_onboarding.paypal] +client_id = "" +client_secret = "" +partner_id = "" diff --git a/migrations/2023-11-30-170902_add-authorizations-table/down.sql b/migrations/2023-11-30-170902_add-authorizations-table/down.sql new file mode 100644 index 000000000000..476f16a52aab --- /dev/null +++ b/migrations/2023-11-30-170902_add-authorizations-table/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE IF EXISTS incremental_authorization; \ No newline at end of file diff --git a/migrations/2023-11-30-170902_add-authorizations-table/up.sql b/migrations/2023-11-30-170902_add-authorizations-table/up.sql new file mode 100644 index 000000000000..ade615877dc2 --- /dev/null +++ b/migrations/2023-11-30-170902_add-authorizations-table/up.sql @@ -0,0 +1,16 @@ +-- Your SQL goes here + +CREATE TABLE IF NOT EXISTS incremental_authorization ( + authorization_id VARCHAR(64) NOT NULL, + merchant_id VARCHAR(64) NOT NULL, + payment_id VARCHAR(64) NOT NULL, + amount BIGINT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP, + modified_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP, + status VARCHAR(64) NOT NULL, + error_code VARCHAR(255), + error_message TEXT, + connector_authorization_id VARCHAR(64), + previously_authorized_amount BIGINT NOT NULL, + PRIMARY KEY (authorization_id, merchant_id) +); \ No newline at end of file diff --git a/migrations/2023-12-01-090834_add-authorization_count-in-payment_intent/down.sql b/migrations/2023-12-01-090834_add-authorization_count-in-payment_intent/down.sql new file mode 100644 index 000000000000..8d4d0e6d81d2 --- /dev/null +++ b/migrations/2023-12-01-090834_add-authorization_count-in-payment_intent/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_intent DROP COLUMN IF EXISTS authorization_count; \ No newline at end of file diff --git a/migrations/2023-12-01-090834_add-authorization_count-in-payment_intent/up.sql b/migrations/2023-12-01-090834_add-authorization_count-in-payment_intent/up.sql new file mode 100644 index 000000000000..741135c6a1a3 --- /dev/null +++ b/migrations/2023-12-01-090834_add-authorization_count-in-payment_intent/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE payment_intent ADD COLUMN IF NOT EXISTS authorization_count INTEGER; diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index f5ad99f05752..f77638a43db5 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -2888,6 +2888,15 @@ "no_three_ds" ] }, + "AuthorizationStatus": { + "type": "string", + "enum": [ + "success", + "failure", + "processing", + "unresolved" + ] + }, "BacsBankTransfer": { "type": "object", "required": [ @@ -4316,6 +4325,11 @@ "type": "string", "description": "The card holder's name", "example": "John Test" + }, + "card_cvc": { + "type": "string", + "description": "The CVC number for the card", + "nullable": true } } }, @@ -5102,6 +5116,14 @@ ], "nullable": true }, + "surcharge_details": { + "allOf": [ + { + "$ref": "#/components/schemas/SurchargeDetailsResponse" + } + ], + "nullable": true + }, "requires_cvv": { "type": "boolean", "description": "Whether this payment method requires CVV to be collected", @@ -6414,6 +6436,44 @@ } } }, + "IncrementalAuthorizationResponse": { + "type": "object", + "required": [ + "authorization_id", + "amount", + "status", + "previously_authorized_amount" + ], + "properties": { + "authorization_id": { + "type": "string", + "description": "The unique identifier of authorization" + }, + "amount": { + "type": "integer", + "format": "int64", + "description": "Amount the authorization has been made for" + }, + "status": { + "$ref": "#/components/schemas/AuthorizationStatus" + }, + "error_code": { + "type": "string", + "description": "Error code sent by the connector for authorization", + "nullable": true + }, + "error_message": { + "type": "string", + "description": "Error message sent by the connector for authorization", + "nullable": true + }, + "previously_authorized_amount": { + "type": "integer", + "format": "int64", + "description": "Previously authorized amount for the payment" + } + } + }, "IndomaretVoucherData": { "type": "object", "required": [ @@ -9252,7 +9312,7 @@ "payment_method_types": { "type": "array", "items": { - "$ref": "#/components/schemas/PaymentMethodType" + "$ref": "#/components/schemas/RequestPaymentMethodTypes" }, "description": "Subtype of payment method", "example": [ @@ -9545,7 +9605,8 @@ }, "card_cvc": { "type": "string", - "description": "This is used when payment is to be confirmed and the card is not saved", + "description": "This is used when payment is to be confirmed and the card is not saved.\nThis field will be deprecated soon, use the CardToken object instead", + "deprecated": true, "nullable": true }, "shipping": { @@ -9914,7 +9975,8 @@ }, "card_cvc": { "type": "string", - "description": "This is used when payment is to be confirmed and the card is not saved", + "description": "This is used when payment is to be confirmed and the card is not saved.\nThis field will be deprecated soon, use the CardToken object instead", + "deprecated": true, "nullable": true }, "shipping": { @@ -10533,6 +10595,20 @@ "type": "boolean", "description": "If true incremental authorization can be performed on this payment", "nullable": true + }, + "authorization_count": { + "type": "integer", + "format": "int32", + "description": "Total number of authorizations happened in an incremental_authorization payment", + "nullable": true + }, + "incremental_authorizations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IncrementalAuthorizationResponse" + }, + "description": "List of incremental authorizations happened to the payment", + "nullable": true } } }, @@ -11508,6 +11584,76 @@ } } }, + "RequestPaymentMethodTypes": { + "type": "object", + "required": [ + "payment_method_type", + "recurring_enabled", + "installment_payment_enabled" + ], + "properties": { + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethodType" + }, + "payment_experience": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentExperience" + } + ], + "nullable": true + }, + "card_networks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CardNetwork" + }, + "nullable": true + }, + "accepted_currencies": { + "allOf": [ + { + "$ref": "#/components/schemas/AcceptedCurrencies" + } + ], + "nullable": true + }, + "accepted_countries": { + "allOf": [ + { + "$ref": "#/components/schemas/AcceptedCountries" + } + ], + "nullable": true + }, + "minimum_amount": { + "type": "integer", + "format": "int32", + "description": "Minimum amount supported by the processor. To be represented in the lowest denomination of the target currency (For example, for USD it should be in cents)", + "example": 1, + "nullable": true + }, + "maximum_amount": { + "type": "integer", + "format": "int32", + "description": "Maximum amount supported by the processor. To be represented in the lowest denomination of\nthe target currency (For example, for USD it should be in cents)", + "example": 1313, + "nullable": true + }, + "recurring_enabled": { + "type": "boolean", + "description": "Boolean to enable recurring payments / mandates. Default is true.", + "default": true, + "example": false + }, + "installment_payment_enabled": { + "type": "boolean", + "description": "Boolean to enable installment / EMI / BNPL payments. Default is true.", + "default": true, + "example": false + } + } + }, "RequestSurchargeDetails": { "type": "object", "required": [ @@ -11975,6 +12121,106 @@ } } }, + "SurchargeDetailsResponse": { + "type": "object", + "required": [ + "surcharge", + "display_surcharge_amount", + "display_tax_on_surcharge_amount", + "display_total_surcharge_amount", + "display_final_amount" + ], + "properties": { + "surcharge": { + "$ref": "#/components/schemas/SurchargeResponse" + }, + "tax_on_surcharge": { + "allOf": [ + { + "$ref": "#/components/schemas/SurchargePercentage" + } + ], + "nullable": true + }, + "display_surcharge_amount": { + "type": "number", + "format": "double", + "description": "surcharge amount for this payment" + }, + "display_tax_on_surcharge_amount": { + "type": "number", + "format": "double", + "description": "tax on surcharge amount for this payment" + }, + "display_total_surcharge_amount": { + "type": "number", + "format": "double", + "description": "sum of display_surcharge_amount and display_tax_on_surcharge_amount" + }, + "display_final_amount": { + "type": "number", + "format": "double", + "description": "sum of original amount," + } + } + }, + "SurchargePercentage": { + "type": "object", + "required": [ + "percentage" + ], + "properties": { + "percentage": { + "type": "number", + "format": "float" + } + } + }, + "SurchargeResponse": { + "oneOf": [ + { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "fixed" + ] + }, + "value": { + "type": "integer", + "format": "int64", + "description": "Fixed Surcharge value" + } + } + }, + { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "rate" + ] + }, + "value": { + "$ref": "#/components/schemas/SurchargePercentage" + } + } + } + ], + "discriminator": { + "propertyName": "type" + } + }, "SwishQrData": { "type": "object" }, diff --git a/postman/collection-json/adyen_uk.postman_collection.json b/postman/collection-json/adyen_uk.postman_collection.json index 400f04241c27..04a7e39f15e7 100644 --- a/postman/collection-json/adyen_uk.postman_collection.json +++ b/postman/collection-json/adyen_uk.postman_collection.json @@ -13959,10 +13959,10 @@ "// Response body should have value \"invalid_request\" for \"error type\"", "if (jsonData?.error?.message) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'error.message' matches 'The payment has not succeeded yet. Please pass a successful payment to initiate refund'\",", + " \"[POST]::/payments - Content check if value for 'error.message' matches 'This Payment could not be refund because it has a status of requires_confirmation. The expected state is succeeded, partially_captured'\",", " function () {", " pm.expect(jsonData.error.message).to.eql(", - " \"The payment has not succeeded yet. Please pass a successful payment to initiate refund\",", + " \"This Payment could not be refund because it has a status of requires_confirmation. The expected state is succeeded, partially_captured\",", " );", " },", " );", diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index 7ed5e65151e1..1246c51d8eb3 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -25,7 +25,7 @@ function find_prev_connector() { eval "$2='aci'" } -payment_gateway=$1; +payment_gateway=$(echo $1 | tr '[:upper:]' '[:lower:]') base_url=$2; payment_gateway_camelcase="$(tr '[:lower:]' '[:upper:]' <<< ${payment_gateway:0:1})${payment_gateway:1}" src="crates/router/src" @@ -49,7 +49,7 @@ git checkout $conn.rs $src/types/api.rs $src/configs/settings.rs config/developm # Add enum for this connector in required places previous_connector='' -find_prev_connector $1 previous_connector +find_prev_connector $payment_gateway previous_connector previous_connector_camelcase="$(tr '[:lower:]' '[:upper:]' <<< ${previous_connector:0:1})${previous_connector:1}" sed -i'' -e "s|pub mod $previous_connector;|pub mod $previous_connector;\npub mod ${payment_gateway};|" $conn.rs sed -i'' -e "s/};/${payment_gateway}::${payment_gateway_camelcase},\n};/" $conn.rs From b7e6a97e02cf8c1fece3b16700a660e63cf2ad61 Mon Sep 17 00:00:00 2001 From: Sampras lopes Date: Fri, 8 Dec 2023 18:00:59 +0530 Subject: [PATCH 11/19] chore(lints): fix hack lints --- crates/router/src/connector/signifyd.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/router/src/connector/signifyd.rs b/crates/router/src/connector/signifyd.rs index 90ed3143d0e4..703f76b30a82 100644 --- a/crates/router/src/connector/signifyd.rs +++ b/crates/router/src/connector/signifyd.rs @@ -1,6 +1,7 @@ pub mod transformers; use std::fmt::Debug; +#[cfg(feature = "frm")] use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use masking::PeekInterface; From f5dd6b0da27fe5570d7c0296ad0458de121dbb30 Mon Sep 17 00:00:00 2001 From: Sampras lopes Date: Fri, 8 Dec 2023 19:59:20 +0530 Subject: [PATCH 12/19] fix(postman): fix bankofamerica postman collection json --- .../Payments - Create/event.test.js | 4 +-- .../Payments - Create/request.json | 2 +- .../Payments - Retrieve/event.test.js | 4 +-- .../Payments - Confirm/event.test.js | 4 +-- .../Payments - Create/request.json | 2 +- .../Payments - Retrieve/event.test.js | 4 +-- .../Payments - Confirm/event.test.js | 4 +-- .../Payments - Retrieve/event.test.js | 4 +-- .../Payments - Capture/event.test.js | 6 ++-- .../Payments - Create/request.json | 2 +- .../Payments - Retrieve/event.test.js | 6 ++-- .../bankofamerica.postman_collection.json | 34 +++++++++---------- 12 files changed, 38 insertions(+), 38 deletions(-) diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js index ac3f862e43f4..e7f91410f313 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/event.test.js @@ -63,9 +63,9 @@ if (jsonData?.client_secret) { // Response body should have value "processing" for "status" because payment gets succeeded after one day. if (jsonData?.status) { pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'processing'", + "[POST]::/payments - Content check if value for 'status' matches 'succeeded'", function () { - pm.expect(jsonData.status).to.eql("processing"); + pm.expect(jsonData.status).to.eql("succeeded"); }, ); } diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json index 21f054843897..e37391b78b5c 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json @@ -25,7 +25,7 @@ "business_label": "default", "capture_method": "automatic", "capture_on": "2022-09-10T10:11:12Z", - "amount_to_capture": 1, + "amount_to_capture": 6540, "customer_id": "bernard123", "email": "guest@example.com", "name": "John Doe", diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.test.js b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.test.js index a6976d95f69e..81ce7784e832 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.test.js +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.test.js @@ -63,9 +63,9 @@ if (jsonData?.client_secret) { // Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments/:id - Content check if value for 'status' matches 'processing'", + "[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'", function () { - pm.expect(jsonData.status).to.eql("processing"); + pm.expect(jsonData.status).to.eql("succeeded"); }, ); } diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js index b160ad9dc04b..9b9d104698f4 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Confirm/event.test.js @@ -86,9 +86,9 @@ if (jsonData?.amount) { // Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'processing'", + "[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'", function () { - pm.expect(jsonData.status).to.eql("processing"); + pm.expect(jsonData.status).to.eql("succeeded"); }, ); } diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json index b1d5ad5ebbf8..c2a8a615d1c2 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Create/request.json @@ -25,7 +25,7 @@ "business_label": "default", "capture_method": "automatic", "capture_on": "2022-09-10T10:11:12Z", - "amount_to_capture": 1, + "amount_to_capture": 6540, "customer_id": "bernard123", "email": "guest@example.com", "name": "John Doe", diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.test.js b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.test.js index f87069589f0a..80343f371a60 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.test.js +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.test.js @@ -63,9 +63,9 @@ if (jsonData?.client_secret) { // Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments:id - Content check if value for 'status' matches 'processing'", + "[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'", function () { - pm.expect(jsonData.status).to.eql("processing"); + pm.expect(jsonData.status).to.eql("succeeded"); }, ); } diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.test.js b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.test.js index 255743af78c7..91c871e2160c 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.test.js +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/event.test.js @@ -65,9 +65,9 @@ if (jsonData?.client_secret) { // Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments:id/confirm - Content check if value for 'status' matches 'processing'", + "[POST]::/payments:id/confirm - Content check if value for 'status' matches 'succeeded'", function () { - pm.expect(jsonData.status).to.eql("processing"); + pm.expect(jsonData.status).to.eql("succeeded"); }, ); } diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/event.test.js b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/event.test.js index 4fbefdb8494a..ef09f8071d62 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/event.test.js +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/event.test.js @@ -63,9 +63,9 @@ if (jsonData?.client_secret) { // Response body should have value "processing" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments:id - Content check if value for 'status' matches 'processing'", + "[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'", function () { - pm.expect(jsonData.status).to.eql("processing"); + pm.expect(jsonData.status).to.eql("succeeded"); }, ); } diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Capture/event.test.js b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Capture/event.test.js index fa6deebe16a8..ea70c2aace0e 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Capture/event.test.js +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Capture/event.test.js @@ -63,12 +63,12 @@ if (jsonData?.client_secret) { ); } -// Response body should have value "processing" for "status" +// Response body should have value "partially_captured" for "status" if (jsonData?.status) { pm.test( - "[POST]:://payments/:id/capture - Content check if value for 'status' matches 'processing'", + "[POST]:://payments/:id/capture - Content check if value for 'status' matches 'partially_captured'", function () { - pm.expect(jsonData.status).to.eql("processing"); + pm.expect(jsonData.status).to.eql("partially_captured"); }, ); } diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Create/request.json b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Create/request.json index 5e3ff0e70ad2..878ea581a938 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Create/request.json +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Create/request.json @@ -25,7 +25,7 @@ "business_label": "default", "capture_method": "manual", "capture_on": "2022-09-10T10:11:12Z", - "amount_to_capture": 1, + "amount_to_capture": 6540, "customer_id": "bernard123", "email": "guest@example.com", "name": "John Doe", diff --git a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Retrieve/event.test.js b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Retrieve/event.test.js index b1b53a360e32..cc4a60507a89 100644 --- a/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Retrieve/event.test.js +++ b/postman/collection-dir/bankofamerica/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Retrieve/event.test.js @@ -60,12 +60,12 @@ if (jsonData?.client_secret) { ); } -// Response body should have value "processing" for "status" +// Response body should have value "partially_captured" for "status" if (jsonData?.status) { pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'processing'", + "[POST]::/payments - Content check if value for 'status' matches 'partially_captured'", function () { - pm.expect(jsonData.status).to.eql("processing"); + pm.expect(jsonData.status).to.eql("partially_captured"); }, ); } diff --git a/postman/collection-json/bankofamerica.postman_collection.json b/postman/collection-json/bankofamerica.postman_collection.json index 01524d91953d..cfef0bd7a2f0 100644 --- a/postman/collection-json/bankofamerica.postman_collection.json +++ b/postman/collection-json/bankofamerica.postman_collection.json @@ -2391,9 +2391,9 @@ "// Response body should have value \"processing\" for \"status\" because payment gets succeeded after one day.", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'processing'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"processing\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -2518,9 +2518,9 @@ "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'processing'\",", + " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"processing\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -2792,9 +2792,9 @@ "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'processing'\",", + " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"processing\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -2957,9 +2957,9 @@ "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments:id - Content check if value for 'status' matches 'processing'\",", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"processing\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -3221,9 +3221,9 @@ "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments:id/confirm - Content check if value for 'status' matches 'processing'\",", + " \"[POST]::/payments:id/confirm - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"processing\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -3377,9 +3377,9 @@ "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments:id - Content check if value for 'status' matches 'processing'\",", + " \"[POST]::/payments:id - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"processing\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -3622,9 +3622,9 @@ "// Response body should have value \"processing\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]:://payments/:id/capture - Content check if value for 'status' matches 'processing'\",", + " \"[POST]:://payments/:id/capture - Content check if value for 'status' matches 'succeeded'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"processing\");", + " pm.expect(jsonData.status).to.eql(\"succeeded\");", " },", " );", "}", @@ -3766,12 +3766,12 @@ " );", "}", "", - "// Response body should have value \"processing\" for \"status\"", + "// Response body should have value \"partially_captured\" for \"status\"", "if (jsonData?.status) {", " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'processing'\",", + " \"[POST]::/payments - Content check if value for 'status' matches 'partially_captured'\",", " function () {", - " pm.expect(jsonData.status).to.eql(\"processing\");", + " pm.expect(jsonData.status).to.eql(\"partially_captured\");", " },", " );", "}", From 50a2eaed321851fdd86bd275313d9b5bf515f056 Mon Sep 17 00:00:00 2001 From: Sampras lopes Date: Mon, 11 Dec 2023 18:34:08 +0530 Subject: [PATCH 13/19] chore(events): strip prefix from flow type --- crates/common_utils/src/request.rs | 8 +++++--- crates/router/src/events/connector_api_logs.rs | 10 +++++++--- crates/router/src/services/api.rs | 4 ++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/common_utils/src/request.rs b/crates/common_utils/src/request.rs index 0b2cd691d6dd..e3aecb4b5695 100644 --- a/crates/common_utils/src/request.rs +++ b/crates/common_utils/src/request.rs @@ -195,9 +195,11 @@ impl RequestBody { pub fn get_inner_value(request_body: RequestContent) -> Secret { match request_body { - RequestContent::Json(i) - | RequestContent::FormUrlEncoded(i) - | RequestContent::Xml(i) => serde_json::to_string(&i).unwrap_or_default().into(), + RequestContent::Json(i) => serde_json::to_string(&i).unwrap_or_default().into(), + RequestContent::FormUrlEncoded(i) => { + serde_urlencoded::to_string(&i).unwrap_or_default().into() + } + RequestContent::Xml(i) => quick_xml::se::to_string(&i).unwrap_or_default().into(), RequestContent::FormData(_) => String::new().into(), } } diff --git a/crates/router/src/events/connector_api_logs.rs b/crates/router/src/events/connector_api_logs.rs index 38f972089ad9..871a7af0d772 100644 --- a/crates/router/src/events/connector_api_logs.rs +++ b/crates/router/src/events/connector_api_logs.rs @@ -24,19 +24,23 @@ impl ConnectorEvent { #[allow(clippy::too_many_arguments)] pub fn new( connector_name: String, - flow: String, + flow: &str, request: serde_json::Value, response: Option, url: String, method: Method, payment_id: String, merchant_id: String, - request_id: &Option, + request_id: Option<&RequestId>, latency: u128, ) -> Self { Self { connector_name, - flow, + flow: flow + .rsplit_once("::") + .map(|(_, s)| s) + .unwrap_or(flow) + .to_string(), request: request.to_string(), response, url, diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 99b6d61e3017..6aec5de9aba1 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -386,14 +386,14 @@ where let connector_event = ConnectorEvent::new( req.connector.clone(), - std::any::type_name::().to_string(), + std::any::type_name::(), masked_request_body, None, request_url, request_method, req.payment_id.clone(), req.merchant_id.clone(), - &state.request_id, + state.request_id.as_ref(), external_latency, ); From ee3e3f454c106377e5744b02c692a456db0dbfda Mon Sep 17 00:00:00 2001 From: Sampras lopes Date: Mon, 11 Dec 2023 18:46:25 +0530 Subject: [PATCH 14/19] chore(events): remove commented code --- crates/router/src/core/webhooks.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index d58e0ab86582..762ee19b6415 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -780,13 +780,6 @@ pub async fn trigger_webhook_to_merchant( let outgoing_webhooks_signature = transformed_outgoing_webhook .get_outgoing_webhooks_signature(business_profile.payment_response_hash_key.clone())?; - // let transformed_outgoing_webhook_string = router_types::RequestBody::log_and_get_request_body( - // &transformed_outgoing_webhook, - // Encode::::encode_to_string_of_json, - // ) - // .change_context(errors::WebhooksFlowError::OutgoingWebhookEncodingFailed) - // .attach_printable("There was an issue when encoding the outgoing webhook body")?; - let mut header = vec![( reqwest::header::CONTENT_TYPE.to_string(), "application/json".into(), From 17c112653f7d39ef752cdd92966d0bd69c8c9aeb Mon Sep 17 00:00:00 2001 From: Sampras lopes Date: Tue, 12 Dec 2023 14:40:51 +0530 Subject: [PATCH 15/19] fix(events): fix default values for request body --- config/development.toml | 2 +- crates/pm_auth/src/types/api.rs | 2 +- crates/router/src/connector/cybersource.rs | 10 ---------- crates/router/src/services/api.rs | 7 ++++--- 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/config/development.toml b/config/development.toml index 090751b2ea3b..b85658bf1200 100644 --- a/config/development.toml +++ b/config/development.toml @@ -510,7 +510,7 @@ intent_analytics_topic = "hyperswitch-payment-intent-events" attempt_analytics_topic = "hyperswitch-payment-attempt-events" refund_analytics_topic = "hyperswitch-refund-events" api_logs_topic = "hyperswitch-api-log-events" -connector_events_topic = "hyperswitch-connector-api-events" +connector_logs_topic = "hyperswitch-connector-api-events" [analytics] source = "sqlx" diff --git a/crates/pm_auth/src/types/api.rs b/crates/pm_auth/src/types/api.rs index 159c1e816539..3684e34ec052 100644 --- a/crates/pm_auth/src/types/api.rs +++ b/crates/pm_auth/src/types/api.rs @@ -39,7 +39,7 @@ pub trait ConnectorIntegration: ConnectorIntegrationAny, ) -> CustomResult { - Ok(RequestContent::Json(Box::new(serde_json::json!(r#""#)))) + Ok(RequestContent::Json(Box::new(serde_json::json!(r#"{}"#)))) } fn build_request( diff --git a/crates/router/src/connector/cybersource.rs b/crates/router/src/connector/cybersource.rs index e7b68f759a96..f74ab55595dd 100644 --- a/crates/router/src/connector/cybersource.rs +++ b/crates/router/src/connector/cybersource.rs @@ -466,13 +466,6 @@ impl ConnectorIntegration CustomResult { - Ok(RequestContent::Json(Box::new(serde_json::json!({})))) - } fn build_request( &self, req: &types::PaymentsSyncRouterData, @@ -818,9 +811,6 @@ impl ConnectorIntegration: ConnectorIntegrationAny, _connectors: &Connectors, ) -> CustomResult { - Ok(RequestContent::Json(Box::new(json!(r#""#)))) + Ok(RequestContent::Json(Box::new(json!(r#"{}"#)))) } fn get_request_form_data( @@ -287,7 +287,8 @@ where Some(RequestContent::Json(payload)) | Some(RequestContent::FormUrlEncoded(payload)) | Some(RequestContent::Xml(payload)) => payload.masked_serialize().unwrap_or_default(), - _ => serde_json::Value::Null, + Some(RequestContent::FormData(_)) => json!({"request_type": "FORM_DATA"}), + None => serde_json::Value::Null, }); logger::debug!(connector_request_body=?masked_conn_req); logger::debug!(payment_id=?req.payment_id); @@ -374,7 +375,7 @@ where .unwrap_or(json!({ "error": "failed to mask serialize"})), RequestContent::FormData(_) => json!({"request_type": "FORM_DATA"}), }, - None => json!({"error": "EMPTY_REQUEST_BODY"}), + None => serde_json::Value::Null, }; let request_url = request.url.clone(); let request_method = request.method; From d02ee42022759ac851adfe08a884f12a11b01a19 Mon Sep 17 00:00:00 2001 From: Sampras lopes Date: Tue, 12 Dec 2023 18:08:14 +0530 Subject: [PATCH 16/19] chore(deps): bump main branch --- connector-template/mod.rs | 2 +- crates/router/src/connector/placetopay.rs | 29 ++++++++--------------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/connector-template/mod.rs b/connector-template/mod.rs index e9945a726a95..77627f31e53e 100644 --- a/connector-template/mod.rs +++ b/connector-template/mod.rs @@ -304,7 +304,7 @@ impl &self, _req: &types::PaymentsCaptureRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) } diff --git a/crates/router/src/connector/placetopay.rs b/crates/router/src/connector/placetopay.rs index 71f07d79fb6d..1079a45794e4 100644 --- a/crates/router/src/connector/placetopay.rs +++ b/crates/router/src/connector/placetopay.rs @@ -2,6 +2,7 @@ pub mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use masking::ExposeInterface; use transformers as placetopay; @@ -165,7 +166,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = placetopay::PlacetopayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -173,12 +174,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(placetopay_req)) + Ok(RequestContent::Json(Box::new(req_obj))) } fn build_request( @@ -196,7 +192,7 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) } @@ -333,7 +329,7 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let connector_router_data = placetopay::PlacetopayRouterData::try_from(( &self.get_currency_unit(), req.request.currency, @@ -404,12 +400,7 @@ impl ConnectorIntegration::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(placetopay_req)) + Ok(RequestContent::Json(Box::new(req_obj))) } fn build_request( @@ -424,7 +415,7 @@ impl ConnectorIntegration Date: Tue, 12 Dec 2023 20:02:28 +0530 Subject: [PATCH 17/19] chore(events): merge main branch --- crates/router/src/connector/placetopay.rs | 2 +- crates/router/src/connector/riskified.rs | 47 +++++++---------------- crates/router/src/connector/signifyd.rs | 2 +- crates/router/src/types.rs | 2 +- docker-compose-development.yml | 2 - docker-compose.yml | 4 -- 6 files changed, 16 insertions(+), 43 deletions(-) diff --git a/crates/router/src/connector/placetopay.rs b/crates/router/src/connector/placetopay.rs index 1079a45794e4..1b80bcf7f79c 100644 --- a/crates/router/src/connector/placetopay.rs +++ b/crates/router/src/connector/placetopay.rs @@ -21,7 +21,7 @@ use crate::{ api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, }, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] diff --git a/crates/router/src/connector/riskified.rs b/crates/router/src/connector/riskified.rs index e34d12def02a..01c73ff4e663 100644 --- a/crates/router/src/connector/riskified.rs +++ b/crates/router/src/connector/riskified.rs @@ -1,6 +1,7 @@ pub mod transformers; use std::fmt::Debug; +use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, PeekInterface}; use ring::hmac; @@ -22,7 +23,7 @@ use crate::{ use crate::{ services, types::{api::fraud_check as frm_api, fraud_check as frm_types, ErrorResponse, Response}, - utils::{self, BytesExt}, + utils::BytesExt, }; #[derive(Debug, Clone)] @@ -59,9 +60,7 @@ where let auth: riskified::RiskifiedAuthType = riskified::RiskifiedAuthType::try_from(&req.connector_auth_type)?; - let riskified_req = self - .get_request_body(req, connectors)? - .ok_or(errors::ConnectorError::RequestEncodingFailed)?; + let riskified_req = self.get_request_body(req, connectors)?; let binding = types::RequestBody::get_inner_value(riskified_req); let payload = binding.peek(); @@ -157,14 +156,9 @@ impl &self, req: &frm_types::FrmCheckoutRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let req_obj = riskified::RiskifiedPaymentsCheckoutRequest::try_from(req)?; - let riskified_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(riskified_req)) + Ok(RequestContent::Json(Box::new(req_obj))) } fn build_request( @@ -180,7 +174,7 @@ impl .headers(frm_types::FrmCheckoutType::get_headers( self, req, connectors, )?) - .body(frm_types::FrmCheckoutType::get_request_body( + .set_body(frm_types::FrmCheckoutType::get_request_body( self, req, connectors, )?) .build(), @@ -276,25 +270,15 @@ impl &self, req: &frm_types::FrmTransactionRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { match req.is_payment_successful() { Some(false) => { let req_obj = riskified::TransactionFailedRequest::try_from(req)?; - let riskified_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(riskified_req)) + Ok(RequestContent::Json(Box::new(req_obj))) } Some(true) => { let req_obj = riskified::TransactionSuccessRequest::try_from(req)?; - let riskified_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(riskified_req)) + Ok(RequestContent::Json(Box::new(req_obj))) } None => Err(errors::ConnectorError::FlowNotSupported { flow: "Transaction".to_owned(), @@ -318,7 +302,7 @@ impl .headers(frm_types::FrmTransactionType::get_headers( self, req, connectors, )?) - .body(frm_types::FrmTransactionType::get_request_body( + .set_body(frm_types::FrmTransactionType::get_request_body( self, req, connectors, )?) .build(), @@ -392,14 +376,9 @@ impl &self, req: &frm_types::FrmFulfillmentRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let req_obj = riskified::RiskifiedFullfillmentRequest::try_from(req)?; - let riskified_req = types::RequestBody::log_and_get_request_body( - &req_obj, - utils::Encode::::encode_to_string_of_json, - ) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - Ok(Some(riskified_req)) + Ok(RequestContent::Json(Box::new(req_obj))) } fn build_request( @@ -417,7 +396,7 @@ impl .headers(frm_types::FrmFulfillmentType::get_headers( self, req, connectors, )?) - .body(frm_types::FrmFulfillmentType::get_request_body( + .set_body(frm_types::FrmFulfillmentType::get_request_body( self, req, connectors, )?) .build(), diff --git a/crates/router/src/connector/signifyd.rs b/crates/router/src/connector/signifyd.rs index 3a8308b2f3e4..a875166c54bf 100644 --- a/crates/router/src/connector/signifyd.rs +++ b/crates/router/src/connector/signifyd.rs @@ -464,7 +464,7 @@ impl &self, req: &frm_types::FrmFulfillmentRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult { let req_obj = signifyd::FrmFullfillmentSignifydRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(req_obj.clone()))) } diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index f50a936aac8b..ecbea1e793c6 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -21,7 +21,7 @@ pub use api_models::{ enums::{Connector, PayoutConnectors}, payouts as payout_types, }; -pub use common_utils::request::RequestBody; +pub use common_utils::request::{RequestBody, RequestContent}; use common_utils::{pii, pii::Email}; use data_models::mandates::MandateData; use error_stack::{IntoReport, ResultExt}; diff --git a/docker-compose-development.yml b/docker-compose-development.yml index 500f397cfa30..6dd1576a475f 100644 --- a/docker-compose-development.yml +++ b/docker-compose-development.yml @@ -28,8 +28,6 @@ services: redis-standalone: image: redis:7 - labels: - - redis networks: - router_net ports: diff --git a/docker-compose.yml b/docker-compose.yml index f51a47aee940..9f8e7bb4efba 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,8 +24,6 @@ services: redis-standalone: image: redis:7 - labels: - - redis networks: - router_net ports: @@ -133,8 +131,6 @@ services: - clustered_redis volumes: - ./config/redis.conf:/usr/local/etc/redis/redis.conf - labels: - - redis networks: - router_net ports: From 73e6ad257fcfd5d86031888ef2e5c9ea0f9176fd Mon Sep 17 00:00:00 2001 From: Sampras lopes Date: Tue, 12 Dec 2023 20:46:25 +0530 Subject: [PATCH 18/19] chore(lints): fix hack lints --- crates/router/src/connector/riskified.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/router/src/connector/riskified.rs b/crates/router/src/connector/riskified.rs index 01c73ff4e663..d4c24175dc41 100644 --- a/crates/router/src/connector/riskified.rs +++ b/crates/router/src/connector/riskified.rs @@ -1,6 +1,7 @@ pub mod transformers; use std::fmt::Debug; +#[cfg(feature = "frm")] use common_utils::request::RequestContent; use error_stack::{IntoReport, ResultExt}; use masking::{ExposeInterface, PeekInterface}; From 18f7a33ccd407edc2ccc32c252ab6b3a7d20aa9b Mon Sep 17 00:00:00 2001 From: Sampras lopes Date: Wed, 13 Dec 2023 02:19:31 +0530 Subject: [PATCH 19/19] fix(connector): update connector template --- connector-template/mod.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/connector-template/mod.rs b/connector-template/mod.rs index 77627f31e53e..6258d4370768 100644 --- a/connector-template/mod.rs +++ b/connector-template/mod.rs @@ -15,6 +15,7 @@ use crate::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, ErrorResponse, Response, + RequestContent } }; @@ -158,7 +159,7 @@ impl Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } - fn get_request_body(&self, req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors,) -> CustomResult, errors::ConnectorError> { + fn get_request_body(&self, req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors,) -> CustomResult { let connector_router_data = {{project-name | downcase}}::{{project-name | downcase | pascal_case}}RouterData::try_from(( &self.get_currency_unit(), @@ -187,7 +188,7 @@ impl .headers(types::PaymentsAuthorizeType::get_headers( self, req, connectors, )?) - .body(types::PaymentsAuthorizeType::get_request_body(self, req, connectors)?) + .set_body(types::PaymentsAuthorizeType::get_request_body(self, req, connectors)?) .build(), )) } @@ -321,7 +322,7 @@ impl .headers(types::PaymentsCaptureType::get_headers( self, req, connectors, )?) - .body(types::PaymentsCaptureType::get_request_body(self, req, connectors)?) + .set_body(types::PaymentsCaptureType::get_request_body(self, req, connectors)?) .build(), )) } @@ -376,7 +377,7 @@ impl Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } - fn get_request_body(&self, req: &types::RefundsRouterData, _connectors: &settings::Connectors,) -> CustomResult, errors::ConnectorError> { + fn get_request_body(&self, req: &types::RefundsRouterData, _connectors: &settings::Connectors,) -> CustomResult { let connector_router_data = {{project-name | downcase}}::{{project-name | downcase | pascal_case}}RouterData::try_from(( &self.get_currency_unit(), @@ -396,7 +397,7 @@ impl .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers(self, req, connectors)?) - .body(types::RefundExecuteType::get_request_body(self, req, connectors)?) + .set_body(types::RefundExecuteType::get_request_body(self, req, connectors)?) .build(); Ok(Some(request)) } @@ -444,7 +445,7 @@ impl .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) - .body(types::RefundSyncType::get_request_body(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body(self, req, connectors)?) .build(), )) }