From 33cb687d5e82e4046af6a7bbce529d97318916f5 Mon Sep 17 00:00:00 2001 From: Kashif Date: Wed, 22 May 2024 17:37:20 +0530 Subject: [PATCH] refactor(refund): add validation for charge options --- crates/router/src/core/refunds.rs | 82 ++++++++++++++++++++- crates/router/src/core/refunds/validator.rs | 31 +++++++- crates/router/src/core/utils.rs | 52 +------------ 3 files changed, 113 insertions(+), 52 deletions(-) diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index f05772be9d38..b9861ea1f23a 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -5,8 +5,9 @@ use std::collections::HashMap; #[cfg(feature = "olap")] use api_models::admin::MerchantConnectorInfo; -use common_utils::ext_traits::AsyncExt; +use common_utils::ext_traits::{AsyncExt, ValueExt}; use error_stack::{report, ResultExt}; +use masking::PeekInterface; use router_env::{instrument, tracing}; use scheduler::{consumer::types::process_data, utils as process_tracker_utils}; #[cfg(feature = "olap")] @@ -16,7 +17,7 @@ use crate::{ consts, core::{ errors::{self, ConnectorErrorExt, RouterResponse, RouterResult, StorageErrorExt}, - payments::{self, access_token}, + payments::{self, access_token, types::PaymentCharges}, utils as core_utils, }, db, logger, @@ -28,6 +29,7 @@ use crate::{ domain, storage::{self, enums}, transformers::{ForeignFrom, ForeignInto}, + ChargeRefunds, }, utils::{self, OptionExt}, workflows::payment_sync, @@ -128,6 +130,7 @@ pub async fn refund_create_core( .map(services::ApplicationResponse::Json) } +#[allow(clippy::too_many_arguments)] #[instrument(skip_all)] pub async fn trigger_refund_to_gateway( state: &AppState, @@ -137,6 +140,7 @@ pub async fn trigger_refund_to_gateway( payment_attempt: &storage::PaymentAttempt, payment_intent: &storage::PaymentIntent, creds_identifier: Option, + charges: Option, ) -> RouterResult { let routed_through = payment_attempt .connector @@ -179,6 +183,7 @@ pub async fn trigger_refund_to_gateway( payment_attempt, refund, creds_identifier, + charges, ) .await?; @@ -458,6 +463,7 @@ pub async fn sync_refund_with_gateway( payment_attempt, refund, creds_identifier, + None, ) .await?; @@ -588,6 +594,39 @@ pub async fn validate_and_create_refund( ) -> RouterResult { let db = &*state.store; + // Validate charge_id and refund options + let charges = match ( + payment_intent.charges.as_ref(), + payment_attempt.charge_id.as_ref(), + ) { + (Some(charges), Some(charge_id)) => { + let refund_charge_request = req.charges.clone().get_required_value("charges")?; + utils::when(*charge_id != refund_charge_request.charge_id, || { + Err(report!(errors::ApiErrorResponse::InvalidDataValue { + field_name: "charges.charge_id" + })) + .attach_printable("charge_id sent in request mismatches with original charge_id") + })?; + let payment_charges: PaymentCharges = charges + .peek() + .clone() + .parse_value("PaymentCharges") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to parse charges in to PaymentCharges")?; + let options = validator::validate_charge_refund( + &refund_charge_request, + &payment_charges.charge_type, + )?; + Some(ChargeRefunds { + charge_id: charge_id.to_string(), + charge_type: payment_charges.charge_type, + transfer_account_id: payment_charges.transfer_account_id, + options, + }) + } + _ => None, + }; + // Only for initial dev and testing let refund_type = req.refund_type.unwrap_or_default(); @@ -695,6 +734,7 @@ pub async fn validate_and_create_refund( payment_attempt, payment_intent, creds_identifier, + charges, ) .await? } @@ -867,6 +907,7 @@ pub async fn schedule_refund_execution( payment_attempt: &storage::PaymentAttempt, payment_intent: &storage::PaymentIntent, creds_identifier: Option, + charges: Option, ) -> RouterResult { // refunds::RefundResponse> { let db = &*state.store; @@ -903,6 +944,7 @@ pub async fn schedule_refund_execution( payment_attempt, payment_intent, creds_identifier, + charges, ) .await } @@ -1082,6 +1124,41 @@ pub async fn trigger_refund_execute_workflow( .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + let charges = match ( + payment_intent.charges.as_ref(), + payment_attempt.charge_id.as_ref(), + ) { + (Some(charges), Some(charge_id)) => { + let refund_charge_request = + refund.charges.clone().get_required_value("charges")?; + utils::when(*charge_id != refund_charge_request.charge_id, || { + Err(report!(errors::ApiErrorResponse::InvalidDataValue { + field_name: "charges.charge_id" + })) + .attach_printable( + "charge_id sent in request mismatches with original charge_id", + ) + })?; + let payment_charges: PaymentCharges = charges + .peek() + .clone() + .parse_value("PaymentCharges") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to parse charges in to PaymentCharges")?; + let options = validator::validate_charge_refund( + &refund_charge_request, + &payment_charges.charge_type, + )?; + Some(ChargeRefunds { + charge_id: charge_id.to_string(), + charge_type: payment_charges.charge_type, + transfer_account_id: payment_charges.transfer_account_id, + options, + }) + } + _ => None, + }; + //trigger refund request to gateway let updated_refund = trigger_refund_to_gateway( state, @@ -1091,6 +1168,7 @@ pub async fn trigger_refund_execute_workflow( &payment_attempt, &payment_intent, None, + charges, ) .await?; add_refund_sync_task( diff --git a/crates/router/src/core/refunds/validator.rs b/crates/router/src/core/refunds/validator.rs index 49248ae4fea5..3788b8ac26d1 100644 --- a/crates/router/src/core/refunds/validator.rs +++ b/crates/router/src/core/refunds/validator.rs @@ -4,7 +4,11 @@ use time::PrimitiveDateTime; use crate::{ core::errors::{self, CustomResult, RouterResult}, - types::storage::{self, enums}, + types::{ + self, + api::enums as api_enums, + storage::{self, enums}, + }, utils::{self, OptionExt}, }; @@ -144,3 +148,28 @@ pub fn validate_for_valid_refunds( _ => Ok(()), } } + +pub fn validate_charge_refund( + charges: &common_utils::types::ChargeRefunds, + charge_type: &api_enums::PaymentChargeType, +) -> RouterResult { + match charge_type { + api_enums::PaymentChargeType::Stripe(api_enums::StripeChargeType::Direct) => Ok( + types::ChargeRefundsOptions::Direct(types::DirectChargeRefund { + revert_platform_fee: charges + .revert_platform_fee + .get_required_value("revert_platform_fee")?, + }), + ), + api_enums::PaymentChargeType::Stripe(api_enums::StripeChargeType::Destination) => Ok( + types::ChargeRefundsOptions::Destination(types::DestinationChargeRefund { + revert_platform_fee: charges + .revert_platform_fee + .get_required_value("revert_platform_fee")?, + revert_transfer: charges + .revert_transfer + .get_required_value("revert_transfer")?, + }), + ), + } +} diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 9897b9ca938a..0950aaf26b28 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -22,16 +22,13 @@ use crate::core::payments; use crate::{ configs::Settings, consts, - core::{ - errors::{self, RouterResult, StorageErrorExt}, - payments::types::PaymentCharges, - }, + core::errors::{self, RouterResult, StorageErrorExt}, db::StorageInterface, routes::AppState, types::{ self, domain, storage::{self, enums}, - ChargeRefunds, PollConfig, + PollConfig, }, utils::{generate_id, generate_uuid, OptionExt, ValueExt}, }; @@ -225,6 +222,7 @@ pub async fn construct_refund_router_data<'a, F>( payment_attempt: &storage::PaymentAttempt, refund: &'a storage::Refund, creds_identifier: Option, + charges: Option, ) -> RouterResult> { let profile_id = get_profile_id_from_business_details( payment_intent.business_country, @@ -301,50 +299,6 @@ pub async fn construct_refund_router_data<'a, F>( field_name: "browser_info", })?; - let charges = match ( - payment_intent.charges.as_ref(), - payment_attempt.charge_id.as_ref(), - ) { - (Some(charges), Some(charge_id)) => { - let payment_charges: PaymentCharges = charges - .peek() - .clone() - .parse_value("PaymentCharges") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to parse charges in to PaymentCharges")?; - - let refund_request = refund.charges.clone().get_required_value("charges")?; - - let options = match payment_charges.charge_type { - api_models::enums::PaymentChargeType::Stripe( - api_models::enums::StripeChargeType::Direct, - ) => types::ChargeRefundsOptions::Direct(types::DirectChargeRefund { - revert_platform_fee: refund_request - .revert_platform_fee - .get_required_value("revert_platform_fee")?, - }), - api_models::enums::PaymentChargeType::Stripe( - api_models::enums::StripeChargeType::Destination, - ) => types::ChargeRefundsOptions::Destination(types::DestinationChargeRefund { - revert_platform_fee: refund_request - .revert_platform_fee - .get_required_value("revert_platform_fee")?, - revert_transfer: refund_request - .revert_transfer - .get_required_value("revert_transfer")?, - }), - }; - - Some(ChargeRefunds { - charge_id: charge_id.to_string(), - charge_type: payment_charges.charge_type, - transfer_account_id: payment_charges.transfer_account_id, - options, - }) - } - _ => None, - }; - let router_data = types::RouterData { flow: PhantomData, merchant_id: merchant_account.merchant_id.clone(),