diff --git a/api-reference/v2/openapi_spec_v2.json b/api-reference/v2/openapi_spec_v2.json index 2537ae0fcc0..7aa4b11047b 100644 --- a/api-reference/v2/openapi_spec_v2.json +++ b/api-reference/v2/openapi_spec_v2.json @@ -13283,6 +13283,23 @@ } } }, + "MerchantConnectorAuthDetails": { + "type": "object", + "description": "Merchant connector details", + "required": [ + "connector_name", + "merchant_connector_creds" + ], + "properties": { + "connector_name": { + "$ref": "#/components/schemas/Connector" + }, + "merchant_connector_creds": { + "type": "object", + "description": "The merchant connector credentials used for the payment" + } + } + }, "MerchantConnectorCreate": { "type": "object", "description": "Create a new Merchant Connector for the merchant account. The connector could be a payment processor / facilitator / acquirer or specialized services like Fraud / Accounting etc.\"", @@ -13418,17 +13435,16 @@ }, "MerchantConnectorDetails": { "type": "object", - "required": [ - "connector_name", - "merchant_connector_creds" - ], "properties": { - "connector_name": { - "$ref": "#/components/schemas/Connector" + "connector_account_details": { + "type": "object", + "description": "Account details of the Connector. You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Useful for storing additional, structured information on an object.", + "nullable": true }, - "merchant_connector_creds": { + "metadata": { "type": "object", - "description": "The merchant connector credentials used for the payment" + "description": "Metadata is useful for storing additional, unstructured information on an object.", + "nullable": true } } }, @@ -18107,7 +18123,7 @@ "merchant_connector_details": { "allOf": [ { - "$ref": "#/components/schemas/MerchantConnectorDetails" + "$ref": "#/components/schemas/MerchantConnectorAuthDetails" } ], "nullable": true @@ -18312,7 +18328,7 @@ "merchant_connector_details": { "allOf": [ { - "$ref": "#/components/schemas/MerchantConnectorDetails" + "$ref": "#/components/schemas/MerchantConnectorAuthDetails" } ], "nullable": true @@ -19106,7 +19122,7 @@ "merchant_connector_details": { "allOf": [ { - "$ref": "#/components/schemas/MerchantConnectorDetails" + "$ref": "#/components/schemas/MerchantConnectorAuthDetails" } ], "nullable": true @@ -19322,7 +19338,7 @@ "merchant_connector_details": { "allOf": [ { - "$ref": "#/components/schemas/MerchantConnectorDetails" + "$ref": "#/components/schemas/MerchantConnectorAuthDetails" } ], "nullable": true @@ -22084,6 +22100,14 @@ "type": "object", "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true + }, + "merchant_connector_details": { + "allOf": [ + { + "$ref": "#/components/schemas/MerchantConnectorAuthDetails" + } + ], + "nullable": true } }, "additionalProperties": false diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 4269f656310..ac0c20e6b89 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -272,24 +272,8 @@ pub struct PaymentsCreateIntentRequest { pub force_3ds_challenge: Option, /// Merchant connector details used to make payments. - pub merchant_connector_details: Option, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] -#[cfg(feature = "v2")] -pub struct MerchantConnectorDetails { - /// The connector used for the payment - #[schema(value_type = Connector)] - pub connector_name: api_enums::Connector, - - /// The merchant connector credentials used for the payment - #[schema(value_type = Object, example = r#"{ - "merchant_connector_creds": { - "auth_type": "HeaderKey", - "api_key":"sk_test_xxxxxexamplexxxxxx12345" - }, - }"#)] - pub merchant_connector_creds: pii::SecretSerdeValue, + #[schema(value_type = Option)] + pub merchant_connector_details: Option, } #[cfg(feature = "v2")] #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] @@ -5361,7 +5345,8 @@ pub struct PaymentsConfirmIntentRequest { pub payment_token: Option, /// Merchant connector details used to make payments. - pub merchant_connector_details: Option, + #[schema(value_type = Option)] + pub merchant_connector_details: Option, } #[cfg(feature = "v2")] @@ -5536,7 +5521,8 @@ pub struct PaymentsRequest { pub is_iframe_redirection_enabled: Option, /// Merchant connector details used to make payments. - pub merchant_connector_details: Option, + #[schema(value_type = Option)] + pub merchant_connector_details: Option, } #[cfg(feature = "v2")] @@ -5614,7 +5600,8 @@ pub struct PaymentsRetrieveRequest { /// If enabled, provides whole connector response pub all_keys_required: Option, /// Merchant connector details used to make payments. - pub merchant_connector_details: Option, + #[schema(value_type = Option)] + pub merchant_connector_details: Option, } #[cfg(feature = "v2")] diff --git a/crates/api_models/src/refunds.rs b/crates/api_models/src/refunds.rs index 8bc3dcb994c..cb147bf11d9 100644 --- a/crates/api_models/src/refunds.rs +++ b/crates/api_models/src/refunds.rs @@ -102,6 +102,10 @@ pub struct RefundsCreateRequest { /// Metadata is useful for storing additional, unstructured information on an object. #[schema(value_type = Option, example = r#"{ "city": "NY", "unit": "245" }"#)] pub metadata: Option, + + /// Merchant connector details used to make payments. + #[schema(value_type = Option)] + pub merchant_connector_details: Option, } #[cfg(feature = "v1")] @@ -116,6 +120,16 @@ pub struct RefundsRetrieveBody { pub force_sync: Option, } +#[cfg(feature = "v2")] +#[derive(Default, Debug, Clone, Deserialize)] +pub struct RefundsRetrievePayload { + /// `force_sync` with the connector to get refund details + pub force_sync: Option, + + /// Merchant connector details used to make payments. + pub merchant_connector_details: Option, +} + #[cfg(feature = "v1")] #[derive(Default, Debug, ToSchema, Clone, Deserialize, Serialize)] pub struct RefundsRetrieveRequest { @@ -149,6 +163,10 @@ pub struct RefundsRetrieveRequest { /// `force_sync` with the connector to get refund details /// (defaults to false) pub force_sync: Option, + + /// Merchant connector details used to make payments. + #[schema(value_type = Option)] + pub merchant_connector_details: Option, } #[derive(Default, Debug, ToSchema, Clone, Deserialize, Serialize)] @@ -323,7 +341,7 @@ pub struct RefundResponse { pub profile_id: common_utils::id_type::ProfileId, /// The merchant_connector_id of the processor through which this payment went through #[schema(value_type = String)] - pub merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId, + pub merchant_connector_id: Option, /// The reference id of the connector for the refund pub connector_refund_reference_id: Option, } diff --git a/crates/common_types/src/domain.rs b/crates/common_types/src/domain.rs index ca6a59abd6a..f41f874cd06 100644 --- a/crates/common_types/src/domain.rs +++ b/crates/common_types/src/domain.rs @@ -88,3 +88,21 @@ pub struct AcquirerConfig { pub struct AcquirerConfigMap(pub HashMap); impl_to_sql_from_sql_json!(AcquirerConfigMap); + +/// Merchant connector details +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] +#[cfg(feature = "v2")] +pub struct MerchantConnectorAuthDetails { + /// The connector used for the payment + #[schema(value_type = Connector)] + pub connector_name: common_enums::connector_enums::Connector, + + /// The merchant connector credentials used for the payment + #[schema(value_type = Object, example = r#"{ + "merchant_connector_creds": { + "auth_type": "HeaderKey", + "api_key":"sk_test_xxxxxexamplexxxxxx12345" + }, + }"#)] + pub merchant_connector_creds: common_utils::pii::SecretSerdeValue, +} diff --git a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs index dcf5695135d..92ec2f51de2 100644 --- a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs +++ b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs @@ -95,7 +95,7 @@ impl MerchantConnectorAccount { #[derive(Clone, Debug)] pub enum MerchantConnectorAccountTypeDetails { MerchantConnectorAccount(Box), - MerchantConnectorDetails(api_models::payments::MerchantConnectorDetails), + MerchantConnectorDetails(common_types::domain::MerchantConnectorAuthDetails), } #[cfg(feature = "v2")] diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index b907d889a88..ef29b084992 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; #[cfg(feature = "v2")] -use api_models::payments::{MerchantConnectorDetails, SessionToken, VaultSessionDetails}; +use api_models::payments::{SessionToken, VaultSessionDetails}; #[cfg(feature = "v1")] use common_types::primitive_wrappers::{ AlwaysRequestExtendedAuthorization, RequestExtendedAuthorizationBool, @@ -865,7 +865,7 @@ where pub payment_address: payment_address::PaymentAddress, pub mandate_data: Option, pub payment_method: Option, - pub merchant_connector_details: Option, + pub merchant_connector_details: Option, } #[cfg(feature = "v2")] @@ -918,7 +918,7 @@ where /// Should the payment status be synced with connector /// This will depend on the payment status and the force sync flag in the request pub should_sync_with_connector: bool, - pub merchant_connector_details: Option, + pub merchant_connector_details: Option, } #[cfg(feature = "v2")] diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index bdf63e1a1ee..4a3c03d69fb 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -191,6 +191,7 @@ Never share your secret api keys. Keep them guarded and secure. common_types::payments::XenditMultipleSplitRequest, common_types::domain::XenditSplitSubMerchantData, common_types::domain::AdyenSplitItem, + common_types::domain::MerchantConnectorAuthDetails, common_types::refunds::StripeSplitRefundRequest, common_utils::types::ChargeRefunds, common_types::payment_methods::PaymentMethodsEnabled, @@ -425,7 +426,6 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::PaymentsListResponseItem, api_models::payments::PaymentRetrieveBody, api_models::payments::PaymentsRetrieveRequest, - api_models::payments::MerchantConnectorDetails, api_models::payments::PaymentsStatusRequest, api_models::payments::PaymentsCaptureRequest, api_models::payments::PaymentsSessionRequest, diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 69488c44a28..5cb47a411b4 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -8884,7 +8884,7 @@ pub trait OperationSessionGetters { #[cfg(feature = "v2")] fn get_merchant_connector_details( &self, - ) -> Option; + ) -> Option; fn get_connector_customer_id(&self) -> Option; fn get_whole_connector_response(&self) -> Option; @@ -9395,7 +9395,7 @@ impl OperationSessionGetters for PaymentIntentData { fn get_merchant_connector_details( &self, - ) -> Option { + ) -> Option { todo!() } @@ -9591,7 +9591,7 @@ impl OperationSessionGetters for PaymentConfirmData { fn get_merchant_connector_details( &self, - ) -> Option { + ) -> Option { self.merchant_connector_details.clone() } @@ -9887,7 +9887,7 @@ impl OperationSessionGetters for PaymentStatusData { fn get_merchant_connector_details( &self, - ) -> Option { + ) -> Option { self.merchant_connector_details.clone() } @@ -10178,7 +10178,7 @@ impl OperationSessionGetters for PaymentCaptureData { fn get_merchant_connector_details( &self, - ) -> Option { + ) -> Option { todo!() } @@ -10612,7 +10612,7 @@ impl OperationSessionGetters for PaymentAttemptListData { } fn get_merchant_connector_details( &self, - ) -> Option { + ) -> Option { todo!() } diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 1ba46ab7d03..6db9510292b 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1602,7 +1602,7 @@ pub async fn get_connector_default( #[cfg(feature = "v2")] pub async fn get_connector_data_from_request( state: &SessionState, - req: Option, + req: Option, ) -> CustomResult { let connector = req .as_ref() diff --git a/crates/router/src/core/refunds_v2.rs b/crates/router/src/core/refunds_v2.rs index 6eb2962c227..4c39be723c5 100644 --- a/crates/router/src/core/refunds_v2.rs +++ b/crates/router/src/core/refunds_v2.rs @@ -98,6 +98,8 @@ pub async fn refund_create_core( tracing::Span::current().record("global_refund_id", global_refund_id.get_string_repr()); + let merchant_connector_details = req.merchant_connector_details.clone(); + Box::pin(validate_and_create_refund( &state, &merchant_context, @@ -106,6 +108,7 @@ pub async fn refund_create_core( amount, req, global_refund_id, + merchant_connector_details, )) .await .map(services::ApplicationResponse::Json) @@ -152,6 +155,9 @@ pub async fn trigger_refund_to_gateway( refunds_validator::validate_for_valid_refunds(payment_attempt, connector.connector_name)?; + let merchant_connector_account = + domain::MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(Box::new(mca)); + let mut router_data = core_utils::construct_refund_router_data( state, connector_enum, @@ -159,7 +165,7 @@ pub async fn trigger_refund_to_gateway( payment_intent, payment_attempt, refund, - &mca, + &merchant_connector_account, ) .await?; @@ -210,6 +216,101 @@ pub async fn trigger_refund_to_gateway( Ok(response) } +#[allow(clippy::too_many_arguments)] +#[instrument(skip_all)] +pub async fn internal_trigger_refund_to_gateway( + state: &SessionState, + refund: &diesel_refund::Refund, + merchant_context: &domain::MerchantContext, + payment_attempt: &storage::PaymentAttempt, + payment_intent: &storage::PaymentIntent, + merchant_connector_details: common_types::domain::MerchantConnectorAuthDetails, +) -> errors::RouterResult { + let storage_scheme = merchant_context.get_merchant_account().storage_scheme; + + let routed_through = payment_attempt + .connector + .clone() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to retrieve connector from payment attempt")?; + + metrics::REFUND_COUNT.add( + 1, + router_env::metric_attributes!(("connector", routed_through.clone())), + ); + + let connector_enum = merchant_connector_details.connector_name; + + let connector: api::ConnectorData = api::ConnectorData::get_connector_by_name( + &state.conf.connectors, + &connector_enum.to_string(), + api::GetToken::Connector, + None, + )?; + + refunds_validator::validate_for_valid_refunds(payment_attempt, connector.connector_name)?; + + let merchant_connector_account = + domain::MerchantConnectorAccountTypeDetails::MerchantConnectorDetails( + merchant_connector_details, + ); + + let mut router_data = core_utils::construct_refund_router_data( + state, + connector_enum, + merchant_context, + payment_intent, + payment_attempt, + refund, + &merchant_connector_account, + ) + .await?; + + let add_access_token_result = + access_token::add_access_token(state, &connector, merchant_context, &router_data, None) + .await?; + + access_token::update_router_data_with_access_token_result( + &add_access_token_result, + &mut router_data, + &payments::CallConnectorAction::Trigger, + ); + + let connector_response = + call_connector_service(state, &connector, add_access_token_result, router_data).await; + + let refund_update = get_refund_update_object( + state, + &connector, + &storage_scheme, + merchant_context, + &connector_response, + ) + .await; + + let response = match refund_update { + Some(refund_update) => state + .store + .update_refund( + refund.to_owned(), + refund_update, + merchant_context.get_merchant_account().storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InternalServerError) + .attach_printable_lazy(|| { + format!( + "Failed while updating refund: refund_id: {}", + refund.id.get_string_repr() + ) + })?, + None => refund.to_owned(), + }; + // Implement outgoing webhooks here + connector_response.to_refund_failed_response()?; + Ok(response) +} + async fn call_connector_service( state: &SessionState, connector: &api::ConnectorData, @@ -594,14 +695,34 @@ pub async fn refund_retrieve_core( }; let response = if should_call_refund(&refund, request.force_sync.unwrap_or(false)) { - Box::pin(sync_refund_with_gateway( - &state, - &merchant_context, - &payment_attempt, - &payment_intent, - &refund, - )) - .await + if state.conf.merchant_id_auth.merchant_id_auth_enabled { + let merchant_connector_details = match request.merchant_connector_details { + Some(details) => details, + None => { + return Err(report!(errors::ApiErrorResponse::MissingRequiredField { + field_name: "merchant_connector_details" + })); + } + }; + Box::pin(internal_sync_refund_with_gateway( + &state, + &merchant_context, + &payment_attempt, + &payment_intent, + &refund, + merchant_connector_details, + )) + .await + } else { + Box::pin(sync_refund_with_gateway( + &state, + &merchant_context, + &payment_attempt, + &payment_intent, + &refund, + )) + .await + } } else { Ok(refund) }?; @@ -660,6 +781,9 @@ pub async fn sync_refund_with_gateway( let connector_enum = mca.connector_name; + let merchant_connector_account = + domain::MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(Box::new(mca)); + let mut router_data = core_utils::construct_refund_router_data::( state, connector_enum, @@ -667,7 +791,7 @@ pub async fn sync_refund_with_gateway( payment_intent, payment_attempt, refund, - &mca, + &merchant_connector_account, ) .await?; @@ -713,6 +837,81 @@ pub async fn sync_refund_with_gateway( Ok(response) } +#[allow(clippy::too_many_arguments)] +#[instrument(skip_all)] +pub async fn internal_sync_refund_with_gateway( + state: &SessionState, + merchant_context: &domain::MerchantContext, + payment_attempt: &storage::PaymentAttempt, + payment_intent: &storage::PaymentIntent, + refund: &diesel_refund::Refund, + merchant_connector_details: common_types::domain::MerchantConnectorAuthDetails, +) -> errors::RouterResult { + let connector_enum = merchant_connector_details.connector_name; + + let connector: api::ConnectorData = api::ConnectorData::get_connector_by_name( + &state.conf.connectors, + &connector_enum.to_string(), + api::GetToken::Connector, + None, + )?; + + let merchant_connector_account = + domain::MerchantConnectorAccountTypeDetails::MerchantConnectorDetails( + merchant_connector_details, + ); + + let mut router_data = core_utils::construct_refund_router_data::( + state, + connector_enum, + merchant_context, + payment_intent, + payment_attempt, + refund, + &merchant_connector_account, + ) + .await?; + + let add_access_token_result = + access_token::add_access_token(state, &connector, merchant_context, &router_data, None) + .await?; + + access_token::update_router_data_with_access_token_result( + &add_access_token_result, + &mut router_data, + &payments::CallConnectorAction::Trigger, + ); + + let connector_response = + call_connector_service(state, &connector, add_access_token_result, router_data) + .await + .to_refund_failed_response()?; + + let connector_response = perform_integrity_check(connector_response); + + let refund_update = + build_refund_update_for_rsync(&connector, merchant_context, connector_response); + + let response = state + .store + .update_refund( + refund.to_owned(), + refund_update, + merchant_context.get_merchant_account().storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::RefundNotFound) + .attach_printable_lazy(|| { + format!( + "Unable to update refund with refund_id: {}", + refund.id.get_string_repr() + ) + })?; + + // Implement outgoing webhook here + Ok(response) +} + pub fn build_refund_update_for_rsync( connector: &api::ConnectorData, merchant_context: &domain::MerchantContext, @@ -834,6 +1033,7 @@ pub async fn validate_and_create_refund( refund_amount: common_utils_types::MinorUnit, req: refunds::RefundsCreateRequest, global_refund_id: id_type::GlobalRefundId, + merchant_connector_details: Option, ) -> errors::RouterResult { let db = &*state.store; @@ -956,6 +1156,7 @@ pub async fn validate_and_create_refund( merchant_context, payment_attempt, payment_intent, + merchant_connector_details, )) .await? } @@ -1002,12 +1203,6 @@ impl ForeignTryFrom for api::RefundResponse { .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable("Profile id not found")?; - let merchant_connector_id = refund - .connector_id - .clone() - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Connector id not found")?; - let connector_name = refund.connector; let connector = Connector::from_str(&connector_name) .change_context(errors::ConnectorError::InvalidConnectorName) @@ -1030,7 +1225,7 @@ impl ForeignTryFrom for api::RefundResponse { created_at: refund.created_at, updated_at: refund.modified_at, connector, - merchant_connector_id, + merchant_connector_id: refund.connector_id, merchant_reference_id: Some(refund.merchant_reference_id), error_details: Some(RefundErrorDetails { code: refund.refund_error_code.unwrap_or_default(), @@ -1052,6 +1247,7 @@ pub async fn schedule_refund_execution( merchant_context: &domain::MerchantContext, payment_attempt: &storage::PaymentAttempt, payment_intent: &storage::PaymentIntent, + merchant_connector_details: Option, ) -> errors::RouterResult { let db = &*state.store; let runner = storage::ProcessTrackerRunner::RefundWorkflowRouter; @@ -1079,14 +1275,38 @@ pub async fn schedule_refund_execution( Ok(refund) } api_models::refunds::RefundType::Instant => { - let update_refund = Box::pin(trigger_refund_to_gateway( - state, - &refund, - merchant_context, - payment_attempt, - payment_intent, - )) - .await; + let update_refund = + if state.conf.merchant_id_auth.merchant_id_auth_enabled { + let merchant_connector_details = + match merchant_connector_details { + Some(details) => details, + None => { + return Err(report!( + errors::ApiErrorResponse::MissingRequiredField { + field_name: "merchant_connector_details" + } + )); + } + }; + Box::pin(internal_trigger_refund_to_gateway( + state, + &refund, + merchant_context, + payment_attempt, + payment_intent, + merchant_connector_details, + )) + .await + } else { + Box::pin(trigger_refund_to_gateway( + state, + &refund, + merchant_context, + payment_attempt, + payment_intent, + )) + .await + }; match update_refund { Ok(updated_refund_data) => { diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 494dae03248..17ac3f9abd0 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -251,7 +251,7 @@ pub async fn construct_refund_router_data<'a, F>( payment_intent: &'a storage::PaymentIntent, payment_attempt: &storage::PaymentAttempt, refund: &'a diesel_refund::Refund, - merchant_connector_account: &MerchantConnectorAccount, + merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails, ) -> RouterResult> { let auth_type = merchant_connector_account .get_connector_account_details() @@ -264,13 +264,17 @@ pub async fn construct_refund_router_data<'a, F>( let payment_method_type = payment_attempt.payment_method_type; - let merchant_connector_account_id = &merchant_connector_account.id; - - let webhook_url = Some(helpers::create_webhook_url( - &state.base_url.clone(), - merchant_context.get_merchant_account().get_id(), - merchant_connector_account_id.get_string_repr(), - )); + let webhook_url = match merchant_connector_account { + domain::MerchantConnectorAccountTypeDetails::MerchantConnectorAccount( + merchant_connector_account, + ) => Some(helpers::create_webhook_url( + &state.base_url.clone(), + merchant_context.get_merchant_account().get_id(), + merchant_connector_account.get_id().get_string_repr(), + )), + // TODO: Implement for connectors that require a webhook URL to be included in the request payload. + domain::MerchantConnectorAccountTypeDetails::MerchantConnectorDetails(_) => None, + }; let supported_connector = &state .conf @@ -314,6 +318,13 @@ pub async fn construct_refund_router_data<'a, F>( let merchant_config_currency = braintree_metadata.and_then(|braintree| braintree.merchant_config_currency); + let connector_wallets_details = match merchant_connector_account { + domain::MerchantConnectorAccountTypeDetails::MerchantConnectorAccount( + merchant_connector_account, + ) => merchant_connector_account.get_connector_wallets_details(), + domain::MerchantConnectorAccountTypeDetails::MerchantConnectorDetails(_) => None, + }; + let router_data = types::RouterData { flow: PhantomData, merchant_id: merchant_context.get_merchant_account().get_id().clone(), @@ -330,7 +341,7 @@ pub async fn construct_refund_router_data<'a, F>( address: PaymentAddress::default(), auth_type: payment_attempt.authentication_type, connector_meta_data: merchant_connector_account.get_metadata(), - connector_wallets_details: merchant_connector_account.get_connector_wallets_details(), + connector_wallets_details, amount_captured: payment_intent .amount_captured .map(|amt| amt.get_amount_as_i64()), diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 8656d3f2890..4e0386f99ee 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -1216,7 +1216,11 @@ impl Refunds { { route = route .service(web::resource("").route(web::post().to(refunds::refunds_create))) - .service(web::resource("/{id}").route(web::get().to(refunds::refunds_retrieve))) + .service( + web::resource("/{id}") + .route(web::get().to(refunds::refunds_retrieve)) + .route(web::post().to(refunds::refunds_retrieve_with_gateway_creds)), + ) .service( web::resource("/{id}/update_metadata") .route(web::put().to(refunds::refunds_metadata_update)), diff --git a/crates/router/src/routes/refunds.rs b/crates/router/src/routes/refunds.rs index 157acb87c19..63b6762b0a6 100644 --- a/crates/router/src/routes/refunds.rs +++ b/crates/router/src/routes/refunds.rs @@ -103,6 +103,21 @@ pub async fn refunds_create( payload, }; + let auth_type = if state.conf.merchant_id_auth.merchant_id_auth_enabled { + &auth::MerchantIdAuth + } else { + auth::auth_type( + &auth::V2ApiKeyAuth { + is_connected_allowed: false, + is_platform_allowed: false, + }, + &auth::JWTAuth { + permission: Permission::ProfileRefundWrite, + }, + req.headers(), + ) + }; + Box::pin(api::server_wrap( flow, state, @@ -119,16 +134,7 @@ pub async fn refunds_create( global_refund_id.clone(), ) }, - auth::auth_type( - &auth::V2ApiKeyAuth { - is_connected_allowed: false, - is_platform_allowed: false, - }, - &auth::JWTAuth { - permission: Permission::ProfileRefundWrite, - }, - req.headers(), - ), + auth_type, api_locking::LockAction::NotApplicable, )) .await @@ -201,6 +207,7 @@ pub async fn refunds_retrieve( let refund_request = refunds::RefundsRetrieveRequest { refund_id: path.into_inner(), force_sync: query_params.force_sync, + merchant_connector_details: None, }; let flow = match query_params.force_sync { Some(true) => Flow::RefundsRetrieveForceSync, @@ -240,6 +247,64 @@ pub async fn refunds_retrieve( .await } +#[cfg(feature = "v2")] +#[instrument(skip_all, fields(flow))] +pub async fn refunds_retrieve_with_gateway_creds( + state: web::Data, + req: HttpRequest, + path: web::Path, + payload: web::Json, +) -> HttpResponse { + let flow = match payload.force_sync { + Some(true) => Flow::RefundsRetrieveForceSync, + _ => Flow::RefundsRetrieve, + }; + + tracing::Span::current().record("flow", flow.to_string()); + + let refund_request = refunds::RefundsRetrieveRequest { + refund_id: path.into_inner(), + force_sync: payload.force_sync, + merchant_connector_details: payload.merchant_connector_details.clone(), + }; + + let auth_type = if state.conf.merchant_id_auth.merchant_id_auth_enabled { + &auth::MerchantIdAuth + } else { + auth::auth_type( + &auth::V2ApiKeyAuth { + is_connected_allowed: false, + is_platform_allowed: false, + }, + &auth::JWTAuth { + permission: Permission::ProfileRefundRead, + }, + req.headers(), + ) + }; + + Box::pin(api::server_wrap( + flow, + state, + &req, + refund_request, + |state, auth: auth::AuthenticationData, refund_request, _| { + let merchant_context = domain::MerchantContext::NormalMerchant(Box::new( + domain::Context(auth.merchant_account, auth.key_store), + )); + refund_retrieve_core_with_refund_id( + state, + merchant_context, + auth.profile, + refund_request, + ) + }, + auth_type, + api_locking::LockAction::NotApplicable, + )) + .await +} + #[cfg(feature = "v1")] /// Refunds - Retrieve (POST) ///