diff --git a/Cargo.lock b/Cargo.lock index f65a0fd4be..d835074cfe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3363,8 +3363,7 @@ dependencies = [ [[package]] name = "g2h" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aece561ff748cdd2a37c8ee938a47bbf9b2c03823b393a332110599b14ee827" +source = "git+https://github.com/NishantJoshi00/g2h?branch=fixing-response-serializing#fd2c856b2c6c88a85d6fe51d95b4d3342b788d31" dependencies = [ "cargo_metadata 0.19.2", "heck 0.5.0", @@ -3526,7 +3525,7 @@ dependencies = [ [[package]] name = "grpc-api-types" version = "0.1.0" -source = "git+https://github.com/juspay/connector-service?rev=4918efedd5ea6c33e4a1600b988b2cf4948bed10#4918efedd5ea6c33e4a1600b988b2cf4948bed10" +source = "git+https://github.com/juspay/connector-service?rev=a9f7cd96693fa034ea69d8e21125ea0f76182fae#a9f7cd96693fa034ea69d8e21125ea0f76182fae" dependencies = [ "axum 0.8.4", "error-stack 0.5.0", @@ -6900,7 +6899,7 @@ dependencies = [ [[package]] name = "rust-grpc-client" version = "0.1.0" -source = "git+https://github.com/juspay/connector-service?rev=4918efedd5ea6c33e4a1600b988b2cf4948bed10#4918efedd5ea6c33e4a1600b988b2cf4948bed10" +source = "git+https://github.com/juspay/connector-service?rev=a9f7cd96693fa034ea69d8e21125ea0f76182fae#a9f7cd96693fa034ea69d8e21125ea0f76182fae" dependencies = [ "grpc-api-types", ] diff --git a/crates/external_services/Cargo.toml b/crates/external_services/Cargo.toml index 30ee8ee5a2..d7fa589752 100644 --- a/crates/external_services/Cargo.toml +++ b/crates/external_services/Cargo.toml @@ -53,7 +53,7 @@ reqwest = { version = "0.11.27", features = ["rustls-tls"] } http = "0.2.12" url = { version = "2.5.4", features = ["serde"] } quick-xml = { version = "0.31.0", features = ["serialize"] } -unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "4918efedd5ea6c33e4a1600b988b2cf4948bed10", package = "rust-grpc-client" } +unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "a9f7cd96693fa034ea69d8e21125ea0f76182fae", package = "rust-grpc-client" } # First party crates diff --git a/crates/external_services/src/grpc_client/unified_connector_service.rs b/crates/external_services/src/grpc_client/unified_connector_service.rs index 5a4a912b79..bac66a3bb5 100644 --- a/crates/external_services/src/grpc_client/unified_connector_service.rs +++ b/crates/external_services/src/grpc_client/unified_connector_service.rs @@ -88,6 +88,14 @@ pub enum UnifiedConnectorServiceError { /// Failed to perform Payment Get from gRPC Server #[error("Failed to perform Payment Get from gRPC Server")] PaymentGetFailure, + + /// Failed to perform Payment Setup Mandate from gRPC Server + #[error("Failed to perform Setup Mandate from gRPC Server")] + PaymentRegisterFailure, + + /// Failed to perform Payment Repeat Payment from gRPC Server + #[error("Failed to perform Repeat Payment from gRPC Server")] + PaymentRepeatEverythingFailure, } /// Result type for Dynamic Routing @@ -160,7 +168,10 @@ impl UnifiedConnectorServiceClient { .await; match connect_result { - Ok(Ok(client)) => Some(Self { client }), + Ok(Ok(client)) => { + logger::info!("Successfully connected to Unified Connector Service"); + Some(Self { client }) + } Ok(Err(err)) => { logger::error!(error = ?err, "Failed to connect to Unified Connector Service"); None @@ -217,6 +228,51 @@ impl UnifiedConnectorServiceClient { .change_context(UnifiedConnectorServiceError::PaymentGetFailure) .inspect_err(|error| logger::error!(?error)) } + + /// Performs Payment Setup Mandate + pub async fn payment_setup_mandate( + &self, + payment_register_request: payments_grpc::PaymentServiceRegisterRequest, + connector_auth_metadata: ConnectorAuthMetadata, + grpc_headers: GrpcHeaders, + ) -> UnifiedConnectorServiceResult> + { + let mut request = tonic::Request::new(payment_register_request); + + let metadata = + build_unified_connector_service_grpc_headers(connector_auth_metadata, grpc_headers)?; + *request.metadata_mut() = metadata; + + self.client + .clone() + .register(request) + .await + .change_context(UnifiedConnectorServiceError::PaymentRegisterFailure) + .inspect_err(|error| logger::error!(?error)) + } + + /// Performs Payment repeat (MIT - Merchant Initiated Transaction). + pub async fn payment_repeat( + &self, + payment_repeat_request: payments_grpc::PaymentServiceRepeatEverythingRequest, + connector_auth_metadata: ConnectorAuthMetadata, + grpc_headers: GrpcHeaders, + ) -> UnifiedConnectorServiceResult< + tonic::Response, + > { + let mut request = tonic::Request::new(payment_repeat_request); + + let metadata = + build_unified_connector_service_grpc_headers(connector_auth_metadata, grpc_headers)?; + *request.metadata_mut() = metadata; + + self.client + .clone() + .repeat_everything(request) + .await + .change_context(UnifiedConnectorServiceError::PaymentRepeatEverythingFailure) + .inspect_err(|error| logger::error!(?error)) + } } /// Build the gRPC Headers for Unified Connector Service Request diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 8b80d22dc2..9399c0d3b3 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -88,7 +88,7 @@ reqwest = { version = "0.11.27", features = ["json", "rustls-tls", "gzip", "mult ring = "0.17.14" rust_decimal = { version = "1.37.1", features = ["serde-with-float", "serde-with-str"] } rust-i18n = { git = "https://github.com/kashif-m/rust-i18n", rev = "f2d8096aaaff7a87a847c35a5394c269f75e077a" } -unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "4918efedd5ea6c33e4a1600b988b2cf4948bed10", package = "rust-grpc-client" } +unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "a9f7cd96693fa034ea69d8e21125ea0f76182fae", package = "rust-grpc-client" } rustc-hash = "1.1.0" rustls = "0.22" rustls-pemfile = "2" diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 21215ea723..e077362cc1 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -20,6 +20,7 @@ use crate::{ unified_connector_service::{ build_unified_connector_service_auth_metadata, handle_unified_connector_service_response_for_payment_authorize, + handle_unified_connector_service_response_for_payment_repeat, }, }, logger, @@ -515,51 +516,23 @@ impl Feature for types::PaymentsAu merchant_connector_account: domain::MerchantConnectorAccountTypeDetails, merchant_context: &domain::MerchantContext, ) -> RouterResult<()> { - let client = state - .grpc_client - .unified_connector_service_client - .clone() - .ok_or(ApiErrorResponse::InternalServerError) - .attach_printable("Failed to fetch Unified Connector Service client")?; - - let payment_authorize_request = - payments_grpc::PaymentServiceAuthorizeRequest::foreign_try_from(self) - .change_context(ApiErrorResponse::InternalServerError) - .attach_printable("Failed to construct Payment Authorize Request")?; - - let connector_auth_metadata = build_unified_connector_service_auth_metadata( - merchant_connector_account, - merchant_context, - ) - .change_context(ApiErrorResponse::InternalServerError) - .attach_printable("Failed to construct request metadata")?; - - let response = client - .payment_authorize( - payment_authorize_request, - connector_auth_metadata, - state.get_grpc_headers(), + if self.request.mandate_id.is_some() { + call_unified_connector_service_repeat_payment( + self, + state, + merchant_connector_account, + merchant_context, ) .await - .change_context(ApiErrorResponse::InternalServerError) - .attach_printable("Failed to authorize payment")?; - - let payment_authorize_response = response.into_inner(); - - let (status, router_data_response) = - handle_unified_connector_service_response_for_payment_authorize( - payment_authorize_response.clone(), + } else { + call_unified_connector_service_authorize( + self, + state, + merchant_connector_account, + merchant_context, ) - .change_context(ApiErrorResponse::InternalServerError) - .attach_printable("Failed to deserialize UCS response")?; - - self.status = status; - self.response = router_data_response; - self.raw_connector_response = payment_authorize_response - .raw_connector_response - .map(Secret::new); - - Ok(()) + .await + } } } @@ -846,3 +819,115 @@ async fn process_capture_flow( router_data.response = Ok(updated_response); Ok(router_data) } + +async fn call_unified_connector_service_authorize( + router_data: &mut types::RouterData< + api::Authorize, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + state: &SessionState, + #[cfg(feature = "v1")] merchant_connector_account: helpers::MerchantConnectorAccountType, + #[cfg(feature = "v2")] merchant_connector_account: domain::MerchantConnectorAccountTypeDetails, + merchant_context: &domain::MerchantContext, +) -> RouterResult<()> { + let client = state + .grpc_client + .unified_connector_service_client + .clone() + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to fetch Unified Connector Service client")?; + + let payment_authorize_request = + payments_grpc::PaymentServiceAuthorizeRequest::foreign_try_from(router_data) + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to construct Payment Authorize Request")?; + + let connector_auth_metadata = + build_unified_connector_service_auth_metadata(merchant_connector_account, merchant_context) + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to construct request metadata")?; + + let response = client + .payment_authorize( + payment_authorize_request, + connector_auth_metadata, + state.get_grpc_headers(), + ) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to authorize payment")?; + + let payment_authorize_response = response.into_inner(); + + let (status, router_data_response) = + handle_unified_connector_service_response_for_payment_authorize( + payment_authorize_response.clone(), + ) + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to deserialize UCS response")?; + + router_data.status = status; + router_data.response = router_data_response; + router_data.raw_connector_response = payment_authorize_response + .raw_connector_response + .map(Secret::new); + + Ok(()) +} + +async fn call_unified_connector_service_repeat_payment( + router_data: &mut types::RouterData< + api::Authorize, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + state: &SessionState, + #[cfg(feature = "v1")] merchant_connector_account: helpers::MerchantConnectorAccountType, + #[cfg(feature = "v2")] merchant_connector_account: domain::MerchantConnectorAccountTypeDetails, + merchant_context: &domain::MerchantContext, +) -> RouterResult<()> { + let client = state + .grpc_client + .unified_connector_service_client + .clone() + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to fetch Unified Connector Service client")?; + + let payment_repeat_request = + payments_grpc::PaymentServiceRepeatEverythingRequest::foreign_try_from(router_data) + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to construct Payment Authorize Request")?; + + let connector_auth_metadata = + build_unified_connector_service_auth_metadata(merchant_connector_account, merchant_context) + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to construct request metadata")?; + + let response = client + .payment_repeat( + payment_repeat_request, + connector_auth_metadata, + state.get_grpc_headers(), + ) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to authorize payment")?; + + let payment_repeat_response = response.into_inner(); + + let (status, router_data_response) = + handle_unified_connector_service_response_for_payment_repeat( + payment_repeat_response.clone(), + ) + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to deserialize UCS response")?; + + router_data.status = status; + router_data.response = router_data_response; + router_data.raw_connector_response = payment_repeat_response + .raw_connector_response + .map(Secret::new); + + Ok(()) +} diff --git a/crates/router/src/core/payments/flows/setup_mandate_flow.rs b/crates/router/src/core/payments/flows/setup_mandate_flow.rs index eb43bff64f..6204d52168 100644 --- a/crates/router/src/core/payments/flows/setup_mandate_flow.rs +++ b/crates/router/src/core/payments/flows/setup_mandate_flow.rs @@ -1,19 +1,25 @@ use async_trait::async_trait; use common_types::payments as common_payments_types; +use error_stack::ResultExt; use router_env::logger; +use unified_connector_service_client::payments as payments_grpc; use super::{ConstructFlowSpecificData, Feature}; use crate::{ core::{ - errors::{ConnectorErrorExt, RouterResult}, + errors::{ApiErrorResponse, ConnectorErrorExt, RouterResult}, mandate, payments::{ self, access_token, customers, helpers, tokenization, transformers, PaymentData, }, + unified_connector_service::{ + build_unified_connector_service_auth_metadata, + handle_unified_connector_service_response_for_payment_register, + }, }, routes::SessionState, services, - types::{self, api, domain}, + types::{self, api, domain, transformers::ForeignTryFrom}, }; #[cfg(feature = "v1")] @@ -200,6 +206,62 @@ impl Feature for types::Setup _ => Ok((None, true)), } } + + async fn call_unified_connector_service<'a>( + &mut self, + state: &SessionState, + #[cfg(feature = "v1")] merchant_connector_account: helpers::MerchantConnectorAccountType, + #[cfg(feature = "v2")] + merchant_connector_account: domain::MerchantConnectorAccountTypeDetails, + merchant_context: &domain::MerchantContext, + ) -> RouterResult<()> { + let client = state + .grpc_client + .unified_connector_service_client + .clone() + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to fetch Unified Connector Service client")?; + + let payment_register_request = + payments_grpc::PaymentServiceRegisterRequest::foreign_try_from(self) + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to construct Payment Setup Mandate Request")?; + + let connector_auth_metadata = build_unified_connector_service_auth_metadata( + merchant_connector_account, + merchant_context, + ) + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to construct request metadata")?; + + let response = client + .payment_setup_mandate( + payment_register_request, + connector_auth_metadata, + state.get_grpc_headers(), + ) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to Setup Mandate payment")?; + + let payment_register_response = response.into_inner(); + + let (status, router_data_response) = + handle_unified_connector_service_response_for_payment_register( + payment_register_response.clone(), + ) + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to deserialize UCS response")?; + + self.status = status; + self.response = router_data_response; + // UCS does not return raw connector response for setup mandate right now + // self.raw_connector_response = payment_register_response + // .raw_connector_response + // .map(Secret::new); + + Ok(()) + } } impl mandate::MandateBehaviour for types::SetupMandateRequestData { diff --git a/crates/router/src/core/unified_connector_service.rs b/crates/router/src/core/unified_connector_service.rs index 8c246facaf..7d19975052 100644 --- a/crates/router/src/core/unified_connector_service.rs +++ b/crates/router/src/core/unified_connector_service.rs @@ -10,7 +10,7 @@ use hyperswitch_domain_models::merchant_connector_account::MerchantConnectorAcco use hyperswitch_domain_models::{ merchant_context::MerchantContext, router_data::{ConnectorAuthType, ErrorResponse, RouterData}, - router_response_types::{PaymentsResponseData, RedirectForm}, + router_response_types::PaymentsResponseData, }; use masking::{ExposeInterface, PeekInterface, Secret}; use unified_connector_service_client::payments::{ @@ -238,85 +238,8 @@ pub fn handle_unified_connector_service_response_for_payment_authorize( > { let status = AttemptStatus::foreign_try_from(response.status())?; - let connector_response_reference_id = - response.response_ref_id.as_ref().and_then(|identifier| { - identifier - .id_type - .clone() - .and_then(|id_type| match id_type { - payments_grpc::identifier::IdType::Id(id) => Some(id), - payments_grpc::identifier::IdType::EncodedData(encoded_data) => { - Some(encoded_data) - } - payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None, - }) - }); - - let transaction_id = response.transaction_id.as_ref().and_then(|id| { - id.id_type.clone().and_then(|id_type| match id_type { - payments_grpc::identifier::IdType::Id(id) => Some(id), - payments_grpc::identifier::IdType::EncodedData(encoded_data) => Some(encoded_data), - payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None, - }) - }); - - let router_data_response = match status { - AttemptStatus::Charged | - AttemptStatus::Authorized | - AttemptStatus::AuthenticationPending | - AttemptStatus::DeviceDataCollectionPending | - AttemptStatus::Started | - AttemptStatus::AuthenticationSuccessful | - AttemptStatus::Authorizing | - AttemptStatus::ConfirmationAwaited | - AttemptStatus::Pending => Ok(PaymentsResponseData::TransactionResponse { - resource_id: match transaction_id.as_ref() { - Some(transaction_id) => hyperswitch_domain_models::router_request_types::ResponseId::ConnectorTransactionId(transaction_id.clone()), - None => hyperswitch_domain_models::router_request_types::ResponseId::NoResponseId, - }, - redirection_data: Box::new( - response - .redirection_data - .clone() - .map(RedirectForm::foreign_try_from) - .transpose()? - ), - mandate_reference: Box::new(None), - connector_metadata: None, - network_txn_id: response.network_txn_id.clone(), - connector_response_reference_id, - incremental_authorization_allowed: response.incremental_authorization_allowed, - charges: None, - }), - AttemptStatus::AuthenticationFailed - | AttemptStatus::AuthorizationFailed - | AttemptStatus::Unresolved - | AttemptStatus::Failure => Err(ErrorResponse { - code: response.error_code().to_owned(), - message: response.error_message().to_owned(), - reason: Some(response.error_message().to_owned()), - status_code: 500, - attempt_status: Some(status), - connector_transaction_id: connector_response_reference_id, - network_decline_code: None, - network_advice_code: None, - network_error_message: None, - }), - AttemptStatus::RouterDeclined | - AttemptStatus::CodInitiated | - AttemptStatus::Voided | - AttemptStatus::VoidInitiated | - AttemptStatus::CaptureInitiated | - AttemptStatus::VoidFailed | - AttemptStatus::AutoRefunded | - AttemptStatus::PartialCharged | - AttemptStatus::PartialChargedAndChargeable | - AttemptStatus::PaymentMethodAwaited | - AttemptStatus::CaptureFailed | - AttemptStatus::IntegrityFailure => return Err(UnifiedConnectorServiceError::NotImplemented(format!( - "AttemptStatus {status:?} is not implemented for Unified Connector Service" - )).into()), - }; + let router_data_response = + Result::::foreign_try_from(response)?; Ok((status, router_data_response)) } @@ -329,75 +252,36 @@ pub fn handle_unified_connector_service_response_for_payment_get( > { let status = AttemptStatus::foreign_try_from(response.status())?; - let connector_response_reference_id = - response.response_ref_id.as_ref().and_then(|identifier| { - identifier - .id_type - .clone() - .and_then(|id_type| match id_type { - payments_grpc::identifier::IdType::Id(id) => Some(id), - payments_grpc::identifier::IdType::EncodedData(encoded_data) => { - Some(encoded_data) - } - payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None, - }) - }); - - let router_data_response = match status { - AttemptStatus::Charged | - AttemptStatus::Authorized | - AttemptStatus::AuthenticationPending | - AttemptStatus::DeviceDataCollectionPending | - AttemptStatus::Started | - AttemptStatus::AuthenticationSuccessful | - AttemptStatus::Authorizing | - AttemptStatus::ConfirmationAwaited | - AttemptStatus::Pending => Ok( - PaymentsResponseData::TransactionResponse { - resource_id: match connector_response_reference_id.as_ref() { - Some(connector_response_reference_id) => hyperswitch_domain_models::router_request_types::ResponseId::ConnectorTransactionId(connector_response_reference_id.clone()), - None => hyperswitch_domain_models::router_request_types::ResponseId::NoResponseId, - }, - redirection_data: Box::new( - None - ), - mandate_reference: Box::new(None), - connector_metadata: None, - network_txn_id: response.network_txn_id.clone(), - connector_response_reference_id, - incremental_authorization_allowed: None, - charges: None, - } - ), - AttemptStatus::AuthenticationFailed - | AttemptStatus::AuthorizationFailed - | AttemptStatus::Failure => Err(ErrorResponse { - code: response.error_code().to_owned(), - message: response.error_message().to_owned(), - reason: Some(response.error_message().to_owned()), - status_code: 500, - attempt_status: Some(status), - connector_transaction_id: connector_response_reference_id, - network_decline_code: None, - network_advice_code: None, - network_error_message: None, - }), - AttemptStatus::RouterDeclined | - AttemptStatus::CodInitiated | - AttemptStatus::Voided | - AttemptStatus::VoidInitiated | - AttemptStatus::CaptureInitiated | - AttemptStatus::VoidFailed | - AttemptStatus::AutoRefunded | - AttemptStatus::PartialCharged | - AttemptStatus::PartialChargedAndChargeable | - AttemptStatus::Unresolved | - AttemptStatus::PaymentMethodAwaited | - AttemptStatus::CaptureFailed | - AttemptStatus::IntegrityFailure => return Err(UnifiedConnectorServiceError::NotImplemented(format!( - "AttemptStatus {status:?} is not implemented for Unified Connector Service" - )).into()), - }; + let router_data_response = + Result::::foreign_try_from(response)?; + + Ok((status, router_data_response)) +} + +pub fn handle_unified_connector_service_response_for_payment_register( + response: payments_grpc::PaymentServiceRegisterResponse, +) -> CustomResult< + (AttemptStatus, Result), + UnifiedConnectorServiceError, +> { + let status = AttemptStatus::foreign_try_from(response.status())?; + + let router_data_response = + Result::::foreign_try_from(response)?; + + Ok((status, router_data_response)) +} + +pub fn handle_unified_connector_service_response_for_payment_repeat( + response: payments_grpc::PaymentServiceRepeatEverythingResponse, +) -> CustomResult< + (AttemptStatus, Result), + UnifiedConnectorServiceError, +> { + let status = AttemptStatus::foreign_try_from(response.status())?; + + let router_data_response = + Result::::foreign_try_from(response)?; Ok((status, router_data_response)) } diff --git a/crates/router/src/core/unified_connector_service/transformers.rs b/crates/router/src/core/unified_connector_service/transformers.rs index e937a7e7d2..719b82ffd1 100644 --- a/crates/router/src/core/unified_connector_service/transformers.rs +++ b/crates/router/src/core/unified_connector_service/transformers.rs @@ -6,9 +6,11 @@ use diesel_models::enums as storage_enums; use error_stack::ResultExt; use external_services::grpc_client::unified_connector_service::UnifiedConnectorServiceError; use hyperswitch_domain_models::{ - router_data::RouterData, - router_flow_types::payments::{Authorize, PSync}, - router_request_types::{AuthenticationData, PaymentsAuthorizeData, PaymentsSyncData}, + router_data::{ErrorResponse, RouterData}, + router_flow_types::payments::{Authorize, PSync, SetupMandate}, + router_request_types::{ + AuthenticationData, PaymentsAuthorizeData, PaymentsSyncData, SetupMandateRequestData, + }, router_response_types::{PaymentsResponseData, RedirectForm}, }; use masking::{ExposeInterface, PeekInterface}; @@ -167,6 +169,443 @@ impl ForeignTryFrom<&RouterData> + for payments_grpc::PaymentServiceRegisterRequest +{ + type Error = error_stack::Report; + + fn foreign_try_from( + router_data: &RouterData, + ) -> Result { + let currency = payments_grpc::Currency::foreign_try_from(router_data.request.currency)?; + let payment_method = router_data + .request + .payment_method_type + .map(|payment_method_type| { + build_unified_connector_service_payment_method( + router_data.request.payment_method_data.clone(), + payment_method_type, + ) + }) + .transpose()?; + let address = payments_grpc::PaymentAddress::foreign_try_from(router_data.address.clone())?; + let auth_type = payments_grpc::AuthenticationType::foreign_try_from(router_data.auth_type)?; + let browser_info = router_data + .request + .browser_info + .clone() + .map(payments_grpc::BrowserInformation::foreign_try_from) + .transpose()?; + let setup_future_usage = router_data + .request + .setup_future_usage + .map(payments_grpc::FutureUsage::foreign_try_from) + .transpose()?; + let customer_acceptance = router_data + .request + .customer_acceptance + .clone() + .map(payments_grpc::CustomerAcceptance::foreign_try_from) + .transpose()?; + + Ok(Self { + request_ref_id: Some(Identifier { + id_type: Some(payments_grpc::identifier::IdType::Id( + router_data.connector_request_reference_id.clone(), + )), + }), + currency: currency.into(), + payment_method, + minor_amount: router_data.request.amount, + email: router_data + .request + .email + .clone() + .map(|e| e.expose().expose()), + customer_name: router_data + .request + .customer_name + .clone() + .map(|customer_name| customer_name.peek().to_owned()), + connector_customer_id: router_data + .request + .customer_id + .as_ref() + .map(|id| id.get_string_repr().to_string()), + address: Some(address), + auth_type: auth_type.into(), + enrolled_for_3ds: false, + authentication_data: None, + metadata: router_data + .request + .metadata + .as_ref() + .map(|secret| secret.peek()) + .and_then(|val| val.as_object()) //secret + .map(|map| { + map.iter() + .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string()))) + .collect::>() + }) + .unwrap_or_default(), + return_url: router_data.request.router_return_url.clone(), + webhook_url: router_data.request.webhook_url.clone(), + complete_authorize_url: router_data.request.complete_authorize_url.clone(), + access_token: None, + session_token: None, + order_tax_amount: None, + order_category: None, + merchant_order_reference_id: None, + shipping_cost: router_data + .request + .shipping_cost + .map(|cost| cost.get_amount_as_i64()), + setup_future_usage: setup_future_usage.map(|s| s.into()), + off_session: router_data.request.off_session, + request_incremental_authorization: router_data + .request + .request_incremental_authorization, + request_extended_authorization: None, + customer_acceptance, + browser_info, + payment_experience: None, + }) + } +} + +impl ForeignTryFrom<&RouterData> + for payments_grpc::PaymentServiceRepeatEverythingRequest +{ + type Error = error_stack::Report; + + fn foreign_try_from( + router_data: &RouterData, + ) -> Result { + let currency = payments_grpc::Currency::foreign_try_from(router_data.request.currency)?; + + let mandate_reference = match &router_data.request.mandate_id { + Some(mandate) => match &mandate.mandate_reference_id { + Some(api_models::payments::MandateReferenceId::ConnectorMandateId( + connector_mandate_id, + )) => Some(payments_grpc::MandateReference { + mandate_id: connector_mandate_id.get_connector_mandate_id(), + }), + _ => { + return Err(UnifiedConnectorServiceError::MissingRequiredField { + field_name: "connector_mandate_id", + } + .into()) + } + }, + None => { + return Err(UnifiedConnectorServiceError::MissingRequiredField { + field_name: "connector_mandate_id", + } + .into()) + } + }; + + Ok(Self { + request_ref_id: Some(Identifier { + id_type: Some(payments_grpc::identifier::IdType::Id( + router_data.connector_request_reference_id.clone(), + )), + }), + mandate_reference, + amount: router_data.request.amount, + currency: currency.into(), + minor_amount: router_data.request.amount, + merchant_order_reference_id: router_data.request.merchant_order_reference_id.clone(), + metadata: router_data + .request + .metadata + .as_ref() + .and_then(|val| val.as_object()) + .map(|map| { + map.iter() + .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string()))) + .collect::>() + }) + .unwrap_or_default(), + webhook_url: router_data.request.webhook_url.clone(), + }) + } +} + +impl ForeignTryFrom + for Result +{ + type Error = error_stack::Report; + + fn foreign_try_from( + response: payments_grpc::PaymentServiceAuthorizeResponse, + ) -> Result { + let status = AttemptStatus::foreign_try_from(response.status())?; + + let connector_response_reference_id = + response.response_ref_id.as_ref().and_then(|identifier| { + identifier + .id_type + .clone() + .and_then(|id_type| match id_type { + payments_grpc::identifier::IdType::Id(id) => Some(id), + payments_grpc::identifier::IdType::EncodedData(encoded_data) => { + Some(encoded_data) + } + payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None, + }) + }); + + let transaction_id = response.transaction_id.as_ref().and_then(|id| { + id.id_type.clone().and_then(|id_type| match id_type { + payments_grpc::identifier::IdType::Id(id) => Some(id), + payments_grpc::identifier::IdType::EncodedData(encoded_data) => Some(encoded_data), + payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None, + }) + }); + + let response = if response.error_code.is_some() { + Err(ErrorResponse { + code: response.error_code().to_owned(), + message: response.error_message().to_owned(), + reason: Some(response.error_message().to_owned()), + status_code: 500, //TODO: To be handled once UCS sends proper status codes + attempt_status: Some(status), + connector_transaction_id: connector_response_reference_id, + network_decline_code: None, + network_advice_code: None, + network_error_message: None, + }) + } else { + Ok(PaymentsResponseData::TransactionResponse { + resource_id: match transaction_id.as_ref() { + Some(transaction_id) => hyperswitch_domain_models::router_request_types::ResponseId::ConnectorTransactionId(transaction_id.clone()), + None => hyperswitch_domain_models::router_request_types::ResponseId::NoResponseId, + }, + redirection_data: Box::new( + response + .redirection_data + .clone() + .map(RedirectForm::foreign_try_from) + .transpose()? + ), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: response.network_txn_id.clone(), + connector_response_reference_id, + incremental_authorization_allowed: response.incremental_authorization_allowed, + charges: None, + }) + }; + + Ok(response) + } +} + +impl ForeignTryFrom + for Result +{ + type Error = error_stack::Report; + + fn foreign_try_from( + response: payments_grpc::PaymentServiceGetResponse, + ) -> Result { + let status = AttemptStatus::foreign_try_from(response.status())?; + + let connector_response_reference_id = + response.response_ref_id.as_ref().and_then(|identifier| { + identifier + .id_type + .clone() + .and_then(|id_type| match id_type { + payments_grpc::identifier::IdType::Id(id) => Some(id), + payments_grpc::identifier::IdType::EncodedData(encoded_data) => { + Some(encoded_data) + } + payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None, + }) + }); + + let response = if response.error_code.is_some() { + Err(ErrorResponse { + code: response.error_code().to_owned(), + message: response.error_message().to_owned(), + reason: Some(response.error_message().to_owned()), + status_code: 500, //TODO: To be handled once UCS sends proper status codes + attempt_status: Some(status), + connector_transaction_id: connector_response_reference_id, + network_decline_code: None, + network_advice_code: None, + network_error_message: None, + }) + } else { + Ok(PaymentsResponseData::TransactionResponse { + resource_id: match connector_response_reference_id.as_ref() { + Some(connector_response_reference_id) => hyperswitch_domain_models::router_request_types::ResponseId::ConnectorTransactionId(connector_response_reference_id.clone()), + None => hyperswitch_domain_models::router_request_types::ResponseId::NoResponseId, + }, + redirection_data: Box::new( + None + ), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: response.network_txn_id.clone(), + connector_response_reference_id, + incremental_authorization_allowed: None, + charges: None, + } + ) + }; + + Ok(response) + } +} + +impl ForeignTryFrom + for Result +{ + type Error = error_stack::Report; + + fn foreign_try_from( + response: payments_grpc::PaymentServiceRegisterResponse, + ) -> Result { + let status = AttemptStatus::foreign_try_from(response.status())?; + + let connector_response_reference_id = + response.response_ref_id.as_ref().and_then(|identifier| { + identifier + .id_type + .clone() + .and_then(|id_type| match id_type { + payments_grpc::identifier::IdType::Id(id) => Some(id), + payments_grpc::identifier::IdType::EncodedData(encoded_data) => { + Some(encoded_data) + } + payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None, + }) + }); + + let response = if response.error_code.is_some() { + Err(ErrorResponse { + code: response.error_code().to_owned(), + message: response.error_message().to_owned(), + reason: Some(response.error_message().to_owned()), + status_code: 500, //TODO: To be handled once UCS sends proper status codes + attempt_status: Some(status), + connector_transaction_id: connector_response_reference_id, + network_decline_code: None, + network_advice_code: None, + network_error_message: None, + }) + } else { + Ok(PaymentsResponseData::TransactionResponse { + resource_id: response.registration_id.as_ref().and_then(|identifier| { + identifier + .id_type + .clone() + .and_then(|id_type| match id_type { + payments_grpc::identifier::IdType::Id(id) => Some( + hyperswitch_domain_models::router_request_types::ResponseId::ConnectorTransactionId(id), + ), + payments_grpc::identifier::IdType::EncodedData(encoded_data) => Some( + hyperswitch_domain_models::router_request_types::ResponseId::ConnectorTransactionId(encoded_data), + ), + payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None, + }) + }).unwrap_or(hyperswitch_domain_models::router_request_types::ResponseId::NoResponseId), + redirection_data: Box::new( + response + .redirection_data + .clone() + .map(RedirectForm::foreign_try_from) + .transpose()? + ), + mandate_reference: Box::new( + response.mandate_reference.map(|grpc_mandate| { + hyperswitch_domain_models::router_response_types::MandateReference { + connector_mandate_id: grpc_mandate.mandate_id, + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: None, + } + }) + ), + connector_metadata: None, + network_txn_id: response.network_txn_id, + connector_response_reference_id, + incremental_authorization_allowed: response.incremental_authorization_allowed, + charges: None, + }) + }; + + Ok(response) + } +} + +impl ForeignTryFrom + for Result +{ + type Error = error_stack::Report; + + fn foreign_try_from( + response: payments_grpc::PaymentServiceRepeatEverythingResponse, + ) -> Result { + let status = AttemptStatus::foreign_try_from(response.status())?; + + let connector_response_reference_id = + response.response_ref_id.as_ref().and_then(|identifier| { + identifier + .id_type + .clone() + .and_then(|id_type| match id_type { + payments_grpc::identifier::IdType::Id(id) => Some(id), + payments_grpc::identifier::IdType::EncodedData(encoded_data) => { + Some(encoded_data) + } + payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None, + }) + }); + + let transaction_id = response.transaction_id.as_ref().and_then(|id| { + id.id_type.clone().and_then(|id_type| match id_type { + payments_grpc::identifier::IdType::Id(id) => Some(id), + payments_grpc::identifier::IdType::EncodedData(encoded_data) => Some(encoded_data), + payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None, + }) + }); + + let response = if response.error_code.is_some() { + Err(ErrorResponse { + code: response.error_code().to_owned(), + message: response.error_message().to_owned(), + reason: Some(response.error_message().to_owned()), + status_code: 500, //TODO: To be handled once UCS sends proper status codes + attempt_status: Some(status), + connector_transaction_id: transaction_id, + network_decline_code: None, + network_advice_code: None, + network_error_message: None, + }) + } else { + Ok(PaymentsResponseData::TransactionResponse { + resource_id: match transaction_id.as_ref() { + Some(transaction_id) => hyperswitch_domain_models::router_request_types::ResponseId::ConnectorTransactionId(transaction_id.clone()), + None => hyperswitch_domain_models::router_request_types::ResponseId::NoResponseId, + }, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: response.network_txn_id.clone(), + connector_response_reference_id, + incremental_authorization_allowed: None, + charges: None, + }) + }; + + Ok(response) + } +} + impl ForeignTryFrom for payments_grpc::Currency { type Error = error_stack::Report; @@ -480,3 +919,48 @@ impl ForeignTryFrom for Method { } } } + +impl ForeignTryFrom for payments_grpc::FutureUsage { + type Error = error_stack::Report; + + fn foreign_try_from(future_usage: storage_enums::FutureUsage) -> Result { + match future_usage { + storage_enums::FutureUsage::OnSession => Ok(Self::OnSession), + storage_enums::FutureUsage::OffSession => Ok(Self::OffSession), + } + } +} + +impl ForeignTryFrom + for payments_grpc::CustomerAcceptance +{ + type Error = error_stack::Report; + + fn foreign_try_from( + customer_acceptance: common_types::payments::CustomerAcceptance, + ) -> Result { + let acceptance_type = match customer_acceptance.acceptance_type { + common_types::payments::AcceptanceType::Online => payments_grpc::AcceptanceType::Online, + common_types::payments::AcceptanceType::Offline => { + payments_grpc::AcceptanceType::Offline + } + }; + + let online_mandate_details = + customer_acceptance + .online + .map(|online| payments_grpc::OnlineMandate { + ip_address: online.ip_address.map(|ip| ip.peek().to_string()), + user_agent: online.user_agent, + }); + + Ok(Self { + acceptance_type: acceptance_type.into(), + accepted_at: customer_acceptance + .accepted_at + .map(|dt| dt.assume_utc().unix_timestamp()) + .unwrap_or_default(), + online_mandate_details, + }) + } +}