diff --git a/Cargo.lock b/Cargo.lock index 848ccc6648..f3ab37bbb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1651,6 +1651,20 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.26", + "serde", + "serde_json", + "thiserror 2.0.12", +] + [[package]] name = "cast" version = "0.3.0" @@ -3347,13 +3361,18 @@ dependencies = [ [[package]] name = "g2h" -version = "0.2.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312ad594dd0e3c26f860a52c8b703ab509c546931920f277f1afa9b7127fd755" +checksum = "0aece561ff748cdd2a37c8ee938a47bbf9b2c03823b393a332110599b14ee827" dependencies = [ + "cargo_metadata 0.19.2", "heck 0.5.0", + "proc-macro2", + "prost", "prost-build", + "prost-types", "quote", + "thiserror 2.0.12", "tonic-build", ] @@ -3506,7 +3525,7 @@ dependencies = [ [[package]] name = "grpc-api-types" version = "0.1.0" -source = "git+https://github.com/juspay/connector-service?rev=afaef3427f815befe253da152e5f37d3858bbc9f#afaef3427f815befe253da152e5f37d3858bbc9f" +source = "git+https://github.com/juspay/connector-service?rev=4918efedd5ea6c33e4a1600b988b2cf4948bed10#4918efedd5ea6c33e4a1600b988b2cf4948bed10" dependencies = [ "axum 0.8.4", "error-stack 0.5.0", @@ -6827,7 +6846,7 @@ dependencies = [ name = "router_env" version = "0.1.0" dependencies = [ - "cargo_metadata", + "cargo_metadata 0.18.1", "config", "error-stack 0.4.1", "gethostname", @@ -6880,7 +6899,7 @@ dependencies = [ [[package]] name = "rust-grpc-client" version = "0.1.0" -source = "git+https://github.com/juspay/connector-service?rev=afaef3427f815befe253da152e5f37d3858bbc9f#afaef3427f815befe253da152e5f37d3858bbc9f" +source = "git+https://github.com/juspay/connector-service?rev=4918efedd5ea6c33e4a1600b988b2cf4948bed10#4918efedd5ea6c33e4a1600b988b2cf4948bed10" dependencies = [ "grpc-api-types", ] @@ -9501,7 +9520,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2990d9ea5967266ea0ccf413a4aa5c42a93dbcfda9cb49a97de6931726b12566" dependencies = [ "anyhow", - "cargo_metadata", + "cargo_metadata 0.18.1", "cfg-if 1.0.0", "git2", "regex", diff --git a/crates/external_services/Cargo.toml b/crates/external_services/Cargo.toml index 906f8e9b59..30ee8ee5a2 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 = "afaef3427f815befe253da152e5f37d3858bbc9f", package = "rust-grpc-client" } +unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "4918efedd5ea6c33e4a1600b988b2cf4948bed10", 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 0ad9602985..5a4a912b79 100644 --- a/crates/external_services/src/grpc_client/unified_connector_service.rs +++ b/crates/external_services/src/grpc_client/unified_connector_service.rs @@ -84,6 +84,10 @@ pub enum UnifiedConnectorServiceError { /// Failed to perform Payment Authorize from gRPC Server #[error("Failed to perform Payment Authorize from gRPC Server")] PaymentAuthorizeFailure, + + /// Failed to perform Payment Get from gRPC Server + #[error("Failed to perform Payment Get from gRPC Server")] + PaymentGetFailure, } /// Result type for Dynamic Routing @@ -191,6 +195,28 @@ impl UnifiedConnectorServiceClient { .change_context(UnifiedConnectorServiceError::PaymentAuthorizeFailure) .inspect_err(|error| logger::error!(?error)) } + + /// Performs Payment Sync/Get + pub async fn payment_get( + &self, + payment_get_request: payments_grpc::PaymentServiceGetRequest, + connector_auth_metadata: ConnectorAuthMetadata, + grpc_headers: GrpcHeaders, + ) -> UnifiedConnectorServiceResult> + { + let mut request = tonic::Request::new(payment_get_request); + + let metadata = + build_unified_connector_service_grpc_headers(connector_auth_metadata, grpc_headers)?; + *request.metadata_mut() = metadata; + + self.client + .clone() + .get(request) + .await + .change_context(UnifiedConnectorServiceError::PaymentGetFailure) + .inspect_err(|error| logger::error!(?error)) + } } /// Build the gRPC Headers for Unified Connector Service Request diff --git a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs index 92ec2f51de..1e460ee730 100644 --- a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs +++ b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs @@ -163,7 +163,9 @@ impl MerchantConnectorAccountTypeDetails { Self::MerchantConnectorAccount(merchant_connector_account) => { Some(merchant_connector_account.connector_name) } - Self::MerchantConnectorDetails(_) => None, + Self::MerchantConnectorDetails(merchant_connector_details) => { + Some(merchant_connector_details.connector_name) + } } } diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 585c49ffe1..8b80d22dc2 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 = "afaef3427f815befe253da152e5f37d3858bbc9f", package = "rust-grpc-client" } +unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "4918efedd5ea6c33e4a1600b988b2cf4948bed10", package = "rust-grpc-client" } rustc-hash = "1.1.0" rustls = "0.22" rustls-pemfile = "2" diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 8a0058b79e..5319e82361 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -216,7 +216,27 @@ where let (payment_data, connector_response_data) = match connector { ConnectorCallType::PreDetermined(connector_data) => { - let router_data = call_connector_service( + let (mca_type_details, updated_customer, router_data) = + call_connector_service_prerequisites( + state, + req_state.clone(), + &merchant_context, + connector_data.connector_data.clone(), + &operation, + &mut payment_data, + &customer, + call_connector_action.clone(), + None, + header_payload.clone(), + None, + profile, + false, + false, //should_retry_with_pan is set to false in case of PreDetermined ConnectorCallType + req.should_return_raw_response(), + ) + .await?; + + let router_data = decide_unified_connector_service_call( state, req_state.clone(), &merchant_context, @@ -225,16 +245,17 @@ where &mut payment_data, &customer, call_connector_action.clone(), - None, + None, // schedule_time is not used in PreDetermined ConnectorCallType header_payload.clone(), #[cfg(feature = "frm")] None, - #[cfg(not(feature = "frm"))] - None, profile, false, false, //should_retry_with_pan is set to false in case of PreDetermined ConnectorCallType req.should_return_raw_response(), + mca_type_details, + router_data, + updated_customer, ) .await?; @@ -271,7 +292,28 @@ where ConnectorCallType::Retryable(connectors) => { let mut connectors = connectors.clone().into_iter(); let connector_data = get_connector_data(&mut connectors)?; - let router_data = call_connector_service( + + let (mca_type_details, updated_customer, router_data) = + call_connector_service_prerequisites( + state, + req_state.clone(), + &merchant_context, + connector_data.connector_data.clone(), + &operation, + &mut payment_data, + &customer, + call_connector_action.clone(), + None, + header_payload.clone(), + None, + profile, + false, + false, //should_retry_with_pan is set to false in case of Retryable ConnectorCallType + req.should_return_raw_response(), + ) + .await?; + + let router_data = decide_unified_connector_service_call( state, req_state.clone(), &merchant_context, @@ -280,16 +322,17 @@ where &mut payment_data, &customer, call_connector_action.clone(), - None, + None, // schedule_time is not used in Retryable ConnectorCallType header_payload.clone(), #[cfg(feature = "frm")] None, - #[cfg(not(feature = "frm"))] - None, profile, - false, + true, false, //should_retry_with_pan is set to false in case of PreDetermined ConnectorCallType req.should_return_raw_response(), + mca_type_details, + router_data, + updated_customer, ) .await?; @@ -409,17 +452,37 @@ where .get_connector_from_request(state, &req, &mut payment_data) .await?; - let router_data = internal_call_connector_service( + let merchant_connector_account = payment_data + .get_merchant_connector_details() + .map(domain::MerchantConnectorAccountTypeDetails::MerchantConnectorDetails) + .ok_or_else(|| { + error_stack::report!(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Merchant connector details not found in payment data") + })?; + + operation + .to_domain()? + .populate_payment_data( + state, + &mut payment_data, + &merchant_context, + profile, + &connector_data, + ) + .await?; + + let router_data = connector_service_decider( state, req_state.clone(), &merchant_context, - connector_data, + connector_data.clone(), &operation, &mut payment_data, call_connector_action.clone(), header_payload.clone(), profile, req.should_return_raw_response(), + merchant_connector_account, ) .await?; @@ -3508,27 +3571,29 @@ where router_data = router_data.add_session_token(state, &connector).await?; - let mut should_continue_further = access_token::update_router_data_with_access_token_result( + let should_continue_further = access_token::update_router_data_with_access_token_result( &add_access_token_result, &mut router_data, &call_connector_action, ); - let add_create_order_result = router_data + let should_continue_further = match router_data .create_order_at_connector(state, &connector, should_continue_further) - .await?; + .await? + { + Some(create_order_response) => { + if let Ok(order_id) = create_order_response.clone().create_order_result { + payment_data.set_connector_response_reference_id(Some(order_id.clone())) + } - if add_create_order_result.is_create_order_performed { - if let Ok(order_id_opt) = &add_create_order_result.create_order_result { - payment_data.set_connector_response_reference_id(order_id_opt.clone()); + // Set the response in routerdata response to carry forward + router_data + .update_router_data_with_create_order_response(create_order_response.clone()); + create_order_response.create_order_result.ok().is_some() } - should_continue_further = router_data - .update_router_data_with_create_order_result( - add_create_order_result, - should_continue_further, - ) - .await?; - } + // If create order is not required, then we can proceed with further processing + None => true, + }; let updated_customer = call_create_connector_customer_if_required( state, @@ -3948,6 +4013,9 @@ pub async fn call_connector_service( is_retry_payment: bool, should_retry_with_pan: bool, return_raw_connector_response: Option, + merchant_connector_account_type_details: domain::MerchantConnectorAccountTypeDetails, + mut router_data: RouterData, + updated_customer: Option, ) -> RouterResult> where F: Send + Clone + Sync, @@ -3961,50 +4029,6 @@ where dyn api::Connector: services::api::ConnectorIntegration, { - let stime_connector = Instant::now(); - - let merchant_connector_account = - domain::MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(Box::new( - helpers::get_merchant_connector_account_v2( - state, - merchant_context.get_merchant_key_store(), - connector.merchant_connector_id.as_ref(), - ) - .await?, - )); - - operation - .to_domain()? - .populate_payment_data( - state, - payment_data, - merchant_context, - business_profile, - &connector, - ) - .await?; - - let updated_customer = call_create_connector_customer_if_required( - state, - customer, - merchant_context, - &merchant_connector_account, - payment_data, - ) - .await?; - - let mut router_data = payment_data - .construct_router_data( - state, - connector.connector.id(), - merchant_context, - customer, - &merchant_connector_account, - None, - None, - ) - .await?; - let add_access_token_result = router_data .add_access_token( state, @@ -4016,37 +4040,43 @@ where router_data = router_data.add_session_token(state, &connector).await?; - let mut should_continue_further = access_token::update_router_data_with_access_token_result( + let should_continue_further = access_token::update_router_data_with_access_token_result( &add_access_token_result, &mut router_data, &call_connector_action, ); - let add_create_order_result = router_data + let should_continue = match router_data .create_order_at_connector(state, &connector, should_continue_further) - .await?; + .await? + { + Some(create_order_response) => { + if let Ok(order_id) = create_order_response.clone().create_order_result { + payment_data.set_connector_response_reference_id(Some(order_id)) + } - if add_create_order_result.is_create_order_performed { - if let Ok(order_id_opt) = &add_create_order_result.create_order_result { - payment_data.set_connector_response_reference_id(order_id_opt.clone()); + // Set the response in routerdata response to carry forward + router_data + .update_router_data_with_create_order_response(create_order_response.clone()); + create_order_response.create_order_result.ok().map(|_| ()) } - should_continue_further = router_data - .update_router_data_with_create_order_result( - add_create_order_result, - should_continue_further, - ) - .await?; - } + // If create order is not required, then we can proceed with further processing + None => Some(()), + }; // In case of authorize flow, pre-task and post-tasks are being called in build request // if we do not want to proceed further, then the function will return Ok(None, false) - let (connector_request, should_continue_further) = if should_continue_further { - // Check if the actual flow specific request can be built with available data - router_data - .build_flow_specific_connector_request(state, &connector, call_connector_action.clone()) - .await? - } else { - (None, false) + let (connector_request, should_continue_further) = match should_continue { + Some(_) => { + router_data + .build_flow_specific_connector_request( + state, + &connector, + call_connector_action.clone(), + ) + .await? + } + None => (None, false), }; // Update the payment trackers just before calling the connector @@ -4089,13 +4119,392 @@ where Ok(router_data) }?; - let etime_connector = Instant::now(); - let duration_connector = etime_connector.saturating_duration_since(stime_connector); - tracing::info!(duration = format!("Duration taken: {}", duration_connector.as_millis())); - Ok(router_data) } +#[cfg(feature = "v2")] +#[allow(clippy::too_many_arguments)] +#[allow(clippy::type_complexity)] +#[instrument(skip_all)] +pub async fn call_connector_service_prerequisites( + state: &SessionState, + req_state: ReqState, + merchant_context: &domain::MerchantContext, + connector: api::ConnectorData, + operation: &BoxedOperation<'_, F, ApiRequest, D>, + payment_data: &mut D, + customer: &Option, + call_connector_action: CallConnectorAction, + schedule_time: Option, + header_payload: HeaderPayload, + frm_suggestion: Option, + business_profile: &domain::Profile, + is_retry_payment: bool, + should_retry_with_pan: bool, + all_keys_required: Option, +) -> RouterResult<( + domain::MerchantConnectorAccountTypeDetails, + Option, + RouterData, +)> +where + F: Send + Clone + Sync, + RouterDReq: Send + Sync, + + // To create connector flow specific interface data + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + D: ConstructFlowSpecificData, + RouterData: Feature + Send, + // To construct connector flow specific api + dyn api::Connector: + services::api::ConnectorIntegration, +{ + let merchant_connector_account_type_details = + domain::MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(Box::new( + helpers::get_merchant_connector_account_v2( + state, + merchant_context.get_merchant_key_store(), + connector.merchant_connector_id.as_ref(), + ) + .await?, + )); + + operation + .to_domain()? + .populate_payment_data( + state, + payment_data, + merchant_context, + business_profile, + &connector, + ) + .await?; + + let updated_customer = call_create_connector_customer_if_required( + state, + customer, + merchant_context, + &merchant_connector_account_type_details, + payment_data, + ) + .await?; + + let router_data = payment_data + .construct_router_data( + state, + connector.connector.id(), + merchant_context, + customer, + &merchant_connector_account_type_details, + None, + None, + ) + .await?; + + Ok(( + merchant_connector_account_type_details, + updated_customer, + router_data, + )) +} + +#[cfg(feature = "v2")] +#[instrument(skip_all)] +pub async fn internal_call_connector_service_prerequisites( + state: &SessionState, + merchant_context: &domain::MerchantContext, + connector: api::ConnectorData, + operation: &BoxedOperation<'_, F, ApiRequest, D>, + payment_data: &mut D, + business_profile: &domain::Profile, +) -> RouterResult<( + domain::MerchantConnectorAccountTypeDetails, + RouterData, +)> +where + F: Send + Clone + Sync, + RouterDReq: Send + Sync, + + // To create connector flow specific interface data + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + D: ConstructFlowSpecificData, + RouterData: Feature + Send, + // To construct connector flow specific api + dyn api::Connector: + services::api::ConnectorIntegration, +{ + let merchant_connector_details = + payment_data + .get_merchant_connector_details() + .ok_or_else(|| { + error_stack::report!(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Merchant connector details not found in payment data") + })?; + let merchant_connector_account = + domain::MerchantConnectorAccountTypeDetails::MerchantConnectorDetails( + merchant_connector_details, + ); + + operation + .to_domain()? + .populate_payment_data( + state, + payment_data, + merchant_context, + business_profile, + &connector, + ) + .await?; + + let router_data = payment_data + .construct_router_data( + state, + connector.connector.id(), + merchant_context, + &None, + &merchant_connector_account, + None, + None, + ) + .await?; + + Ok((merchant_connector_account, router_data)) +} + +#[cfg(feature = "v2")] +#[allow(clippy::too_many_arguments)] +#[instrument(skip_all)] +pub async fn connector_service_decider( + state: &SessionState, + req_state: ReqState, + merchant_context: &domain::MerchantContext, + connector: api::ConnectorData, + operation: &BoxedOperation<'_, F, ApiRequest, D>, + payment_data: &mut D, + call_connector_action: CallConnectorAction, + header_payload: HeaderPayload, + business_profile: &domain::Profile, + return_raw_connector_response: Option, + merchant_connector_account_type_details: domain::MerchantConnectorAccountTypeDetails, +) -> RouterResult> +where + F: Send + Clone + Sync, + RouterDReq: Send + Sync, + + // To create connector flow specific interface data + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + D: ConstructFlowSpecificData, + RouterData: Feature + Send, + // To construct connector flow specific api + dyn api::Connector: + services::api::ConnectorIntegration, +{ + let mut router_data = payment_data + .construct_router_data( + state, + connector.connector.id(), + merchant_context, + &None, + &merchant_connector_account_type_details, + None, + None, + ) + .await?; + + // do order creation + let should_call_unified_connector_service = + should_call_unified_connector_service(state, merchant_context, &router_data).await?; + + let (connector_request, should_continue_further) = if !should_call_unified_connector_service { + let mut should_continue_further = true; + + let should_continue = match router_data + .create_order_at_connector(state, &connector, should_continue_further) + .await? + { + Some(create_order_response) => { + if let Ok(order_id) = create_order_response.clone().create_order_result { + payment_data.set_connector_response_reference_id(Some(order_id)) + } + + // Set the response in routerdata response to carry forward + router_data + .update_router_data_with_create_order_response(create_order_response.clone()); + create_order_response.create_order_result.ok().map(|_| ()) + } + // If create order is not required, then we can proceed with further processing + None => Some(()), + }; + + let should_continue: (Option, bool) = match should_continue + { + Some(_) => { + router_data + .build_flow_specific_connector_request( + state, + &connector, + call_connector_action.clone(), + ) + .await? + } + None => (None, false), + }; + should_continue + } else { + // If unified connector service is called, these values are not used + // as the request is built in the unified connector service call + (None, false) + }; + + (_, *payment_data) = operation + .to_update_tracker()? + .update_trackers( + state, + req_state, + payment_data.clone(), + None, // customer is not used in internal flows + merchant_context.get_merchant_account().storage_scheme, + None, + merchant_context.get_merchant_key_store(), + None, // frm_suggestion is not used in internal flows + header_payload.clone(), + ) + .await?; + + record_time_taken_with(|| async { + if should_call_unified_connector_service { + router_data + .call_unified_connector_service( + state, + merchant_connector_account_type_details.clone(), + merchant_context, + ) + .await?; + + Ok(router_data) + } else { + let router_data = if should_continue_further { + router_data + .decide_flows( + state, + &connector, + call_connector_action, + connector_request, + business_profile, + header_payload.clone(), + return_raw_connector_response, + ) + .await + } else { + Ok(router_data) + }?; + Ok(router_data) + } + }) + .await +} + +#[cfg(feature = "v2")] +#[allow(clippy::too_many_arguments)] +#[instrument(skip_all)] +pub async fn decide_unified_connector_service_call( + state: &SessionState, + req_state: ReqState, + merchant_context: &domain::MerchantContext, + connector: api::ConnectorData, + operation: &BoxedOperation<'_, F, ApiRequest, D>, + payment_data: &mut D, + customer: &Option, + call_connector_action: CallConnectorAction, + schedule_time: Option, + header_payload: HeaderPayload, + frm_suggestion: Option, + business_profile: &domain::Profile, + is_retry_payment: bool, + should_retry_with_pan: bool, + return_raw_connector_response: Option, + merchant_connector_account_type_details: domain::MerchantConnectorAccountTypeDetails, + mut router_data: RouterData, + updated_customer: Option, +) -> RouterResult> +where + F: Send + Clone + Sync, + RouterDReq: Send + Sync, + + // To create connector flow specific interface data + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + D: ConstructFlowSpecificData, + RouterData: Feature + Send, + // To construct connector flow specific api + dyn api::Connector: + services::api::ConnectorIntegration, +{ + record_time_taken_with(|| async { + if should_call_unified_connector_service(state, merchant_context, &router_data).await? { + if should_add_task_to_process_tracker(payment_data) { + operation + .to_domain()? + .add_task_to_process_tracker( + state, + payment_data.get_payment_attempt(), + false, + schedule_time, + ) + .await + .map_err(|error| logger::error!(process_tracker_error=?error)) + .ok(); + } + + (_, *payment_data) = operation + .to_update_tracker()? + .update_trackers( + state, + req_state, + payment_data.clone(), + customer.clone(), + merchant_context.get_merchant_account().storage_scheme, + None, + merchant_context.get_merchant_key_store(), + frm_suggestion, + header_payload.clone(), + ) + .await?; + + router_data + .call_unified_connector_service( + state, + merchant_connector_account_type_details.clone(), + merchant_context, + ) + .await?; + + Ok(router_data) + } else { + call_connector_service( + state, + req_state, + merchant_context, + connector, + operation, + payment_data, + customer, + call_connector_action, + schedule_time, + header_payload, + frm_suggestion, + business_profile, + is_retry_payment, + should_retry_with_pan, + return_raw_connector_response, + merchant_connector_account_type_details, + router_data, + updated_customer, + ) + .await + } + }) + .await +} #[cfg(feature = "v1")] // This function does not perform the tokenization action, as the payment method is not saved in this flow. #[allow(clippy::too_many_arguments)] @@ -4393,149 +4802,6 @@ where Ok(router_data) } -#[cfg(feature = "v2")] -#[allow(clippy::too_many_arguments)] -#[instrument(skip_all)] -pub async fn internal_call_connector_service( - state: &SessionState, - req_state: ReqState, - merchant_context: &domain::MerchantContext, - connector: api::ConnectorData, - operation: &BoxedOperation<'_, F, ApiRequest, D>, - payment_data: &mut D, - call_connector_action: CallConnectorAction, - header_payload: HeaderPayload, - business_profile: &domain::Profile, - return_raw_connector_response: Option, -) -> RouterResult> -where - F: Send + Clone + Sync, - RouterDReq: Send + Sync, - - // To create connector flow specific interface data - D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, - D: ConstructFlowSpecificData, - RouterData: Feature + Send, - // To construct connector flow specific api - dyn api::Connector: - services::api::ConnectorIntegration, -{ - let stime_connector = Instant::now(); - - let merchant_connector_details = - payment_data - .get_merchant_connector_details() - .ok_or_else(|| { - error_stack::report!(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Merchant connector details not found in payment data") - })?; - let merchant_connector_account = - domain::MerchantConnectorAccountTypeDetails::MerchantConnectorDetails( - merchant_connector_details, - ); - - operation - .to_domain()? - .populate_payment_data( - state, - payment_data, - merchant_context, - business_profile, - &connector, - ) - .await?; - - let mut router_data = payment_data - .construct_router_data( - state, - connector.connector.id(), - merchant_context, - &None, - &merchant_connector_account, - None, - None, - ) - .await?; - - let add_access_token_result = router_data - .add_access_token( - state, - &connector, - merchant_context, - payment_data.get_creds_identifier(), - ) - .await?; - - router_data = router_data.add_session_token(state, &connector).await?; - - let mut should_continue_further = access_token::update_router_data_with_access_token_result( - &add_access_token_result, - &mut router_data, - &call_connector_action, - ); - - let add_create_order_result = router_data - .create_order_at_connector(state, &connector, should_continue_further) - .await?; - - if add_create_order_result.is_create_order_performed { - if let Ok(order_id_opt) = &add_create_order_result.create_order_result { - payment_data.set_connector_response_reference_id(order_id_opt.clone()); - } - should_continue_further = router_data - .update_router_data_with_create_order_result( - add_create_order_result, - should_continue_further, - ) - .await?; - } - - let (connector_request, should_continue_further) = if should_continue_further { - router_data - .build_flow_specific_connector_request(state, &connector, call_connector_action.clone()) - .await? - } else { - (None, false) - }; - - (_, *payment_data) = operation - .to_update_tracker()? - .update_trackers( - state, - req_state, - payment_data.clone(), - None, - merchant_context.get_merchant_account().storage_scheme, - None, - merchant_context.get_merchant_key_store(), - None, - header_payload.clone(), - ) - .await?; - - let router_data = if should_continue_further { - router_data - .decide_flows( - state, - &connector, - call_connector_action, - connector_request, - business_profile, - header_payload.clone(), - return_raw_connector_response, - ) - .await - } else { - Ok(router_data) - }?; - - let etime_connector = Instant::now(); - let duration_connector = etime_connector.saturating_duration_since(stime_connector); - tracing::info!(duration = format!("Duration taken: {}", duration_connector.as_millis())); - - Ok(router_data) -} - pub async fn add_decrypted_payment_method_token( tokenization_action: TokenizationAction, payment_data: &D, diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index a93e2f391f..2cae2f0600 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -185,35 +185,27 @@ pub trait Feature { _state: &SessionState, _connector: &api::ConnectorData, _should_continue_payment: bool, - ) -> RouterResult + ) -> RouterResult> where F: Clone, Self: Sized, dyn api::Connector: services::ConnectorIntegration, { - Ok(types::CreateOrderResult { - create_order_result: Ok(None), - is_create_order_performed: false, - }) + Ok(None) } - async fn update_router_data_with_create_order_result( + fn update_router_data_with_create_order_response( &mut self, _create_order_result: types::CreateOrderResult, - should_continue_payment: bool, - ) -> RouterResult - where - F: Clone, - Self: Sized, - dyn api::Connector: services::ConnectorIntegration, - { - Ok(should_continue_payment) + ) { } async fn call_unified_connector_service<'a>( &mut self, _state: &SessionState, - _merchant_connector_account: helpers::MerchantConnectorAccountType, + #[cfg(feature = "v1")] _merchant_connector_account: helpers::MerchantConnectorAccountType, + #[cfg(feature = "v2")] + _merchant_connector_account: domain::MerchantConnectorAccountTypeDetails, _merchant_context: &domain::MerchantContext, ) -> RouterResult<()> where diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 1532fffa3f..f42efa0c1e 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -431,7 +431,7 @@ impl Feature for types::PaymentsAu state: &SessionState, connector: &api::ConnectorData, should_continue_payment: bool, - ) -> RouterResult { + ) -> RouterResult> { if connector .connector_name .requires_order_creation_before_payment(self.payment_method) @@ -471,7 +471,7 @@ impl Feature for types::PaymentsAu if let types::PaymentsResponseData::PaymentsCreateOrderResponse { order_id } = res { - Ok(Some(order_id)) + Ok(order_id) } else { Err(error_stack::report!(ApiErrorResponse::InternalServerError) .attach_printable(format!( @@ -482,47 +482,37 @@ impl Feature for types::PaymentsAu Err(error) => Err(error), }; - Ok(types::CreateOrderResult { + Ok(Some(types::CreateOrderResult { create_order_result: create_order_resp, - is_create_order_performed: true, - }) + })) } else { - Ok(types::CreateOrderResult { - create_order_result: Ok(None), - is_create_order_performed: false, - }) + // If the connector does not require order creation, return None + Ok(None) } } - async fn update_router_data_with_create_order_result( + fn update_router_data_with_create_order_response( &mut self, create_order_result: types::CreateOrderResult, - should_continue_further: bool, - ) -> RouterResult { - if create_order_result.is_create_order_performed { - match create_order_result.create_order_result { - Ok(Some(order_id)) => { - self.request.order_id = Some(order_id.clone()); - self.response = - Ok(types::PaymentsResponseData::PaymentsCreateOrderResponse { order_id }); - Ok(true) - } - Ok(None) => Err(error_stack::report!(ApiErrorResponse::InternalServerError) - .attach_printable("Order Id not found."))?, - Err(err) => { - self.response = Err(err.clone()); - Ok(false) - } + ) { + match create_order_result.create_order_result { + Ok(order_id) => { + self.request.order_id = Some(order_id.clone()); // ? why this is assigned here and ucs also wants this to populate data + self.response = + Ok(types::PaymentsResponseData::PaymentsCreateOrderResponse { order_id }); + } + Err(err) => { + self.response = Err(err.clone()); } - } else { - Ok(should_continue_further) } } async fn call_unified_connector_service<'a>( &mut self, state: &SessionState, - merchant_connector_account: helpers::MerchantConnectorAccountType, + #[cfg(feature = "v1")] merchant_connector_account: helpers::MerchantConnectorAccountType, + #[cfg(feature = "v2")] + merchant_connector_account: domain::MerchantConnectorAccountTypeDetails, merchant_context: &domain::MerchantContext, ) -> RouterResult<()> { let client = state @@ -558,13 +548,14 @@ impl Feature for types::PaymentsAu let (status, router_data_response) = handle_unified_connector_service_response_for_payment_authorize( - payment_authorize_response, + payment_authorize_response.clone(), ) .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; Ok(()) } diff --git a/crates/router/src/core/payments/flows/psync_flow.rs b/crates/router/src/core/payments/flows/psync_flow.rs index f2ffbe24d1..e27c997440 100644 --- a/crates/router/src/core/payments/flows/psync_flow.rs +++ b/crates/router/src/core/payments/flows/psync_flow.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; use async_trait::async_trait; +use error_stack::ResultExt; +use unified_connector_service_client::payments as payments_grpc; use super::{ConstructFlowSpecificData, Feature}; use crate::{ @@ -8,10 +10,14 @@ use crate::{ core::{ errors::{ApiErrorResponse, ConnectorErrorExt, RouterResult}, payments::{self, access_token, helpers, transformers, PaymentData}, + unified_connector_service::{ + build_unified_connector_service_auth_metadata, + handle_unified_connector_service_response_for_payment_get, + }, }, routes::SessionState, services::{self, api::ConnectorValidation, logger}, - types::{self, api, domain}, + types::{self, api, domain, transformers::ForeignTryFrom}, }; #[cfg(feature = "v1")] @@ -204,6 +210,56 @@ impl Feature Ok((request, 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_get_request = payments_grpc::PaymentServiceGetRequest::foreign_try_from(self) + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to construct Payment Get 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_get( + payment_get_request, + connector_auth_metadata, + state.get_grpc_headers(), + ) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get payment")?; + + let payment_get_response = response.into_inner(); + + let (status, router_data_response) = + handle_unified_connector_service_response_for_payment_get(payment_get_response.clone()) + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Failed to deserialize UCS response")?; + + self.status = status; + self.response = router_data_response; + self.raw_connector_response = payment_get_response.raw_connector_response; + + Ok(()) + } } #[async_trait] diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 2379dddb8f..5935942908 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -304,7 +304,7 @@ pub async fn construct_payment_router_data_for_authorize<'a>( router_return_url: Some(router_return_url), webhook_url, complete_authorize_url, - customer_id: None, + customer_id: customer_id.clone(), surcharge_details: None, request_extended_authorization: None, request_incremental_authorization: matches!( @@ -3466,8 +3466,126 @@ where impl TryFrom> for types::PaymentsAuthorizeData { type Error = error_stack::Report; - fn try_from(_additional_data: PaymentAdditionalData<'_, F>) -> Result { - todo!() + fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { + let payment_data = additional_data.payment_data.clone(); + let router_base_url = &additional_data.router_base_url; + let connector_name = &additional_data.connector_name; + let attempt = &payment_data.payment_attempt; + let browser_info: Option = attempt + .browser_info + .clone() + .map(types::BrowserInformation::from); + + let complete_authorize_url = Some(helpers::create_complete_authorize_url( + router_base_url, + attempt, + connector_name, + payment_data.creds_identifier.as_deref(), + )); + + let merchant_connector_account_id_or_connector_name = payment_data + .payment_attempt + .merchant_connector_id + .as_ref() + .map(|mca_id| mca_id.get_string_repr()) + .unwrap_or(connector_name); + + let webhook_url = Some(helpers::create_webhook_url( + router_base_url, + &attempt.merchant_id, + merchant_connector_account_id_or_connector_name, + )); + let router_return_url = Some(helpers::create_redirect_url( + router_base_url, + attempt, + connector_name, + payment_data.creds_identifier.as_deref(), + )); + + let payment_method_data = payment_data.payment_method_data.or_else(|| { + if payment_data.mandate_id.is_some() { + Some(domain::PaymentMethodData::MandatePayment) + } else { + None + } + }); + + let amount = payment_data + .payment_attempt + .get_total_amount() + .get_amount_as_i64(); + + let customer_name = additional_data + .customer_data + .as_ref() + .and_then(|customer_data| { + customer_data + .name + .as_ref() + .map(|customer| customer.clone().into_inner()) + }); + + let customer_id = additional_data + .customer_data + .as_ref() + .and_then(|data| data.get_id().clone().try_into().ok()); + + let merchant_order_reference_id = payment_data + .payment_intent + .merchant_reference_id + .map(|s| s.get_string_repr().to_string()); + + let shipping_cost = payment_data.payment_intent.amount_details.shipping_cost; + + Ok(Self { + payment_method_data: payment_method_data + .unwrap_or(domain::PaymentMethodData::Card(domain::Card::default())), + amount, + order_tax_amount: None, // V2 doesn't currently support order tax amount + email: None, // V2 doesn't store email directly in payment_intent + customer_name, + currency: payment_data.currency, + confirm: true, + statement_descriptor_suffix: None, + statement_descriptor: None, + capture_method: Some(payment_data.payment_intent.capture_method), + router_return_url, + webhook_url, + complete_authorize_url, + setup_future_usage: Some(payment_data.payment_intent.setup_future_usage), + mandate_id: payment_data.mandate_id.clone(), + off_session: get_off_session(payment_data.mandate_id.as_ref(), None), + customer_acceptance: None, + setup_mandate_details: None, + browser_info, + order_details: None, + order_category: None, + session_token: None, + enrolled_for_3ds: false, + related_transaction_id: None, + payment_experience: None, + payment_method_type: Some(payment_data.payment_attempt.payment_method_subtype), + surcharge_details: None, + customer_id, + request_incremental_authorization: false, + metadata: payment_data + .payment_intent + .metadata + .clone() + .map(|m| m.expose()), + authentication_data: None, + request_extended_authorization: None, + split_payments: None, + minor_amount: payment_data.payment_attempt.get_total_amount(), + merchant_order_reference_id, + integrity_object: None, + shipping_cost, + additional_payment_method_data: None, + merchant_account_id: None, + merchant_config_currency: None, + connector_testing_data: None, + order_id: None, + }) } } diff --git a/crates/router/src/core/unified_connector_service.rs b/crates/router/src/core/unified_connector_service.rs index 3cdcb28d4d..14836ff20c 100644 --- a/crates/router/src/core/unified_connector_service.rs +++ b/crates/router/src/core/unified_connector_service.rs @@ -1,4 +1,3 @@ -use api_models::admin::ConnectorAuthType; use common_enums::{AttemptStatus, PaymentMethodType}; use common_utils::{errors::CustomResult, ext_traits::ValueExt}; use error_stack::ResultExt; @@ -6,9 +5,11 @@ use external_services::grpc_client::unified_connector_service::{ ConnectorAuthMetadata, UnifiedConnectorServiceError, }; use hyperswitch_connectors::utils::CardData; +#[cfg(feature = "v2")] +use hyperswitch_domain_models::merchant_connector_account::MerchantConnectorAccountTypeDetails; use hyperswitch_domain_models::{ merchant_context::MerchantContext, - router_data::{ErrorResponse, RouterData}, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, router_response_types::{PaymentsResponseData, RedirectForm}, }; use masking::{ExposeInterface, PeekInterface, Secret}; @@ -101,7 +102,7 @@ pub fn build_unified_connector_service_payment_method( } _ => { return Err(UnifiedConnectorServiceError::NotImplemented(format!( - "Unimplemented card payment method type: {payment_method_type:?}" + "Unimplemented payment method subtype: {payment_method_type:?}" )) .into()); } @@ -113,24 +114,52 @@ pub fn build_unified_connector_service_payment_method( })), }) } + hyperswitch_domain_models::payment_method_data::PaymentMethodData::Upi(upi_data) => { + let upi_type = match upi_data { + hyperswitch_domain_models::payment_method_data::UpiData::UpiCollect( + upi_collect_data, + ) => { + let vpa_id = upi_collect_data.vpa_id.map(|vpa| vpa.expose()); + let upi_details = payments_grpc::UpiCollect { vpa_id }; + PaymentMethod::UpiCollect(upi_details) + } + _ => { + return Err(UnifiedConnectorServiceError::NotImplemented(format!( + "Unimplemented payment method subtype: {payment_method_type:?}" + )) + .into()); + } + }; - _ => Err(UnifiedConnectorServiceError::NotImplemented( - "Unimplemented Payment Method".to_string(), - ) + Ok(payments_grpc::PaymentMethod { + payment_method: Some(upi_type), + }) + } + _ => Err(UnifiedConnectorServiceError::NotImplemented(format!( + "Unimplemented payment method: {payment_method_data:?}" + )) .into()), } } pub fn build_unified_connector_service_auth_metadata( - merchant_connector_account: MerchantConnectorAccountType, + #[cfg(feature = "v1")] merchant_connector_account: MerchantConnectorAccountType, + #[cfg(feature = "v2")] merchant_connector_account: MerchantConnectorAccountTypeDetails, merchant_context: &MerchantContext, ) -> CustomResult { + #[cfg(feature = "v1")] let auth_type: ConnectorAuthType = merchant_connector_account .get_connector_account_details() .parse_value("ConnectorAuthType") .change_context(UnifiedConnectorServiceError::FailedToObtainAuthType) .attach_printable("Failed while parsing value for ConnectorAuthType")?; + #[cfg(feature = "v2")] + let auth_type: ConnectorAuthType = merchant_connector_account + .get_connector_account_details() + .change_context(UnifiedConnectorServiceError::FailedToObtainAuthType) + .attach_printable("Failed to obtain ConnectorAuthType")?; + let connector_name = { #[cfg(feature = "v1")] { @@ -211,30 +240,126 @@ pub fn handle_unified_connector_service_response_for_payment_authorize( }) }); + 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 => Ok(PaymentsResponseData::TransactionResponse { - resource_id: match connector_response_reference_id.as_ref() { + 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()), + }; + + Ok((status, router_data_response)) +} + +pub fn handle_unified_connector_service_response_for_payment_get( + response: payments_grpc::PaymentServiceGetResponse, +) -> CustomResult< + (AttemptStatus, Result), + UnifiedConnectorServiceError, +> { + 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( - 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, - }), - _ => Err(ErrorResponse { + 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()), @@ -244,7 +369,22 @@ pub fn handle_unified_connector_service_response_for_payment_authorize( 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()), }; 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 0e2ae6fd34..e937a7e7d2 100644 --- a/crates/router/src/core/unified_connector_service/transformers.rs +++ b/crates/router/src/core/unified_connector_service/transformers.rs @@ -7,8 +7,8 @@ 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, - router_request_types::{AuthenticationData, PaymentsAuthorizeData}, + router_flow_types::payments::{Authorize, PSync}, + router_request_types::{AuthenticationData, PaymentsAuthorizeData, PaymentsSyncData}, router_response_types::{PaymentsResponseData, RedirectForm}, }; use masking::{ExposeInterface, PeekInterface}; @@ -18,6 +18,37 @@ use crate::{ core::unified_connector_service::build_unified_connector_service_payment_method, types::transformers::ForeignTryFrom, }; +impl ForeignTryFrom<&RouterData> + for payments_grpc::PaymentServiceGetRequest +{ + type Error = error_stack::Report; + + fn foreign_try_from( + router_data: &RouterData, + ) -> Result { + let connector_transaction_id = router_data + .request + .connector_transaction_id + .get_connector_transaction_id() + .map(|id| Identifier { + id_type: Some(payments_grpc::identifier::IdType::Id(id)), + }) + .ok(); + + let connector_ref_id = router_data + .request + .connector_reference_id + .clone() + .map(|id| Identifier { + id_type: Some(payments_grpc::identifier::IdType::Id(id)), + }); + + Ok(Self { + transaction_id: connector_transaction_id, + request_ref_id: connector_ref_id, + }) + } +} impl ForeignTryFrom<&RouterData> for payments_grpc::PaymentServiceAuthorizeRequest @@ -113,7 +144,7 @@ impl ForeignTryFrom<&RouterData fn foreign_try_from( payment_address: hyperswitch_domain_models::payment_address::PaymentAddress, ) -> Result { - let shipping = payment_address.get_shipping().and_then(|address| { - let details = address.address.as_ref()?; + let shipping = payment_address.get_shipping().map(|address| { + let details = address.address.as_ref(); let get_str = |opt: &Option>| opt.as_ref().map(|s| s.peek().to_owned()); let get_plain = |opt: &Option| opt.clone(); - let country = details - .country - .as_ref() - .and_then(|c| payments_grpc::CountryAlpha2::from_str_name(&c.to_string())) - .ok_or_else(|| { - UnifiedConnectorServiceError::RequestEncodingFailedWithReason( - "Invalid country code".to_string(), - ) - }) - .attach_printable("Invalid country code") - .ok()? // Return None if invalid - .into(); + let country = details.and_then(|details| { + details + .country + .as_ref() + .and_then(|c| payments_grpc::CountryAlpha2::from_str_name(&c.to_string())) + .map(|country| country.into()) + }); - Some(payments_grpc::Address { - first_name: get_str(&details.first_name), - last_name: get_str(&details.last_name), - line1: get_str(&details.line1), - line2: get_str(&details.line2), - line3: get_str(&details.line3), - city: get_plain(&details.city), - state: get_str(&details.state), - zip_code: get_str(&details.zip), - country_alpha2_code: Some(country), + payments_grpc::Address { + first_name: get_str(&details.and_then(|d| d.first_name.clone())), + last_name: get_str(&details.and_then(|d| d.last_name.clone())), + line1: get_str(&details.and_then(|d| d.line1.clone())), + line2: get_str(&details.and_then(|d| d.line2.clone())), + line3: get_str(&details.and_then(|d| d.line3.clone())), + city: get_plain(&details.and_then(|d| d.city.clone())), + state: get_str(&details.and_then(|d| d.state.clone())), + zip_code: get_str(&details.and_then(|d| d.zip.clone())), + country_alpha2_code: country, email: address.email.as_ref().map(|e| e.peek().to_string()), phone_number: address .phone .as_ref() .and_then(|phone| phone.number.as_ref().map(|n| n.peek().to_string())), phone_country_code: address.phone.as_ref().and_then(|p| p.country_code.clone()), - }) + } }); - let billing = payment_address.get_payment_billing().and_then(|address| { - let details = address.address.as_ref()?; + let billing = payment_address.get_payment_billing().map(|address| { + let details = address.address.as_ref(); let get_str = |opt: &Option>| opt.as_ref().map(|s| s.peek().to_owned()); let get_plain = |opt: &Option| opt.clone(); - let country = details - .country - .as_ref() - .and_then(|c| payments_grpc::CountryAlpha2::from_str_name(&c.to_string())) - .ok_or_else(|| { - UnifiedConnectorServiceError::RequestEncodingFailedWithReason( - "Invalid country code".to_string(), - ) - }) - .attach_printable("Invalid country code") - .ok()? // Return None if invalid - .into(); + let country = details.and_then(|details| { + details + .country + .as_ref() + .and_then(|c| payments_grpc::CountryAlpha2::from_str_name(&c.to_string())) + .map(|country| country.into()) + }); - Some(payments_grpc::Address { - first_name: get_str(&details.first_name), - last_name: get_str(&details.last_name), - line1: get_str(&details.line1), - line2: get_str(&details.line2), - line3: get_str(&details.line3), - city: get_plain(&details.city), - state: get_str(&details.state), - zip_code: get_str(&details.zip), - country_alpha2_code: Some(country), + payments_grpc::Address { + first_name: get_str(&details.and_then(|d| d.first_name.clone())), + last_name: get_str(&details.and_then(|d| d.last_name.clone())), + line1: get_str(&details.and_then(|d| d.line1.clone())), + line2: get_str(&details.and_then(|d| d.line2.clone())), + line3: get_str(&details.and_then(|d| d.line3.clone())), + city: get_plain(&details.and_then(|d| d.city.clone())), + state: get_str(&details.and_then(|d| d.state.clone())), + zip_code: get_str(&details.and_then(|d| d.zip.clone())), + country_alpha2_code: country, email: address.email.as_ref().map(|e| e.peek().to_string()), phone_number: address .phone .as_ref() .and_then(|phone| phone.number.as_ref().map(|n| n.peek().to_string())), phone_country_code: address.phone.as_ref().and_then(|p| p.country_code.clone()), - }) + } }); + let unified_payment_method_billing = + payment_address.get_payment_method_billing().map(|address| { + let details = address.address.as_ref(); + + let get_str = |opt: &Option>| { + opt.as_ref().map(|s| s.peek().to_owned()) + }; + + let get_plain = |opt: &Option| opt.clone(); + + let country = details.and_then(|details| { + details + .country + .as_ref() + .and_then(|c| payments_grpc::CountryAlpha2::from_str_name(&c.to_string())) + .map(|country| country.into()) + }); + + payments_grpc::Address { + first_name: get_str(&details.and_then(|d| d.first_name.clone())), + last_name: get_str(&details.and_then(|d| d.last_name.clone())), + line1: get_str(&details.and_then(|d| d.line1.clone())), + line2: get_str(&details.and_then(|d| d.line2.clone())), + line3: get_str(&details.and_then(|d| d.line3.clone())), + city: get_plain(&details.and_then(|d| d.city.clone())), + state: get_str(&details.and_then(|d| d.state.clone())), + zip_code: get_str(&details.and_then(|d| d.zip.clone())), + country_alpha2_code: country, + email: address.email.as_ref().map(|e| e.peek().to_string()), + phone_number: address + .phone + .as_ref() + .and_then(|phone| phone.number.as_ref().map(|n| n.peek().to_string())), + phone_country_code: address.phone.as_ref().and_then(|p| p.country_code.clone()), + } + }); Ok(Self { - shipping_address: shipping, - billing_address: billing, + shipping_address: shipping.or(unified_payment_method_billing.clone()), + billing_address: billing.or(unified_payment_method_billing), }) } } @@ -391,6 +448,12 @@ impl ForeignTryFrom for RedirectForm { Some(payments_grpc::redirect_form::FormType::Html(html)) => Ok(Self::Html { html_data: html.html_data, }), + Some(payments_grpc::redirect_form::FormType::Uri(_)) => Err( + UnifiedConnectorServiceError::RequestEncodingFailedWithReason( + "URI form type is not implemented".to_string(), + ) + .into(), + ), None => Err( UnifiedConnectorServiceError::RequestEncodingFailedWithReason( "Missing form type".to_string(), diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index a5740561c7..57043bbcc0 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -514,9 +514,9 @@ pub struct PaymentMethodTokenResult { pub connector_response: Option, } +#[derive(Clone)] pub struct CreateOrderResult { - pub create_order_result: Result, ErrorResponse>, - pub is_create_order_performed: bool, + pub create_order_result: Result, } pub struct PspTokenResult {