From d2accdef410319733d6174057bdca468bde1ae83 Mon Sep 17 00:00:00 2001 From: Narayan Bhat <48803246+Narayanbhat166@users.noreply.github.com> Date: Tue, 30 Jan 2024 17:16:02 +0530 Subject: [PATCH] refactor(core): restrict requires_customer_action in confirm (#3235) --- crates/api_models/src/payments.rs | 9 + crates/common_enums/src/enums.rs | 1 + crates/router/src/core/payments.rs | 1 + crates/router/src/core/payments/operations.rs | 1 + .../payments/operations/payment_approve.rs | 1 + .../payments/operations/payment_cancel.rs | 1 + .../payments/operations/payment_capture.rs | 1 + .../operations/payment_complete_authorize.rs | 1 + .../payments/operations/payment_confirm.rs | 38 +- .../payments/operations/payment_create.rs | 1 + .../operations/payment_method_validate.rs | 398 ------------------ .../payments/operations/payment_reject.rs | 1 + .../payments/operations/payment_session.rs | 1 + .../core/payments/operations/payment_start.rs | 1 + .../payments/operations/payment_status.rs | 1 + .../payments/operations/payment_update.rs | 1 + .../payments_incremental_authorization.rs | 1 + crates/router/src/core/webhooks.rs | 2 +- 18 files changed, 51 insertions(+), 410 deletions(-) delete mode 100644 crates/router/src/core/payments/operations/payment_method_validate.rs diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index c856ae3279..3b85644553 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -403,6 +403,15 @@ pub struct HeaderPayload { pub x_hs_latency: Option, } +impl HeaderPayload { + pub fn with_source(payment_confirm_source: api_enums::PaymentSource) -> Self { + Self { + payment_confirm_source: Some(payment_confirm_source), + ..Default::default() + } + } +} + #[derive( Default, Debug, serde::Serialize, Clone, PartialEq, ToSchema, router_derive::PolymorphicSchema, )] diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 7eb65f18a3..7330c0708d 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -2069,6 +2069,7 @@ pub enum PaymentSource { Postman, Dashboard, Sdk, + Webhook, } #[derive( diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 186f760ace..fd265c07da 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -129,6 +129,7 @@ where &merchant_account, &key_store, auth_flow, + header_payload.payment_confirm_source, ) .await?; diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index 716b76120d..89d3131dde 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -110,6 +110,7 @@ pub trait GetTracker: Send { merchant_account: &domain::MerchantAccount, mechant_key_store: &domain::MerchantKeyStore, auth_flow: services::AuthFlow, + payment_confirm_source: Option, ) -> RouterResult>; } diff --git a/crates/router/src/core/payments/operations/payment_approve.rs b/crates/router/src/core/payments/operations/payment_approve.rs index 6d3697caab..ce3998506c 100644 --- a/crates/router/src/core/payments/operations/payment_approve.rs +++ b/crates/router/src/core/payments/operations/payment_approve.rs @@ -42,6 +42,7 @@ impl merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, + _payment_confirm_source: Option, ) -> RouterResult> { let db = &*state.store; let merchant_id = &merchant_account.merchant_id; diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index 9810980cd3..fecf397101 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -42,6 +42,7 @@ impl merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, + _payment_confirm_source: Option, ) -> RouterResult> { let db = &*state.store; let merchant_id = &merchant_account.merchant_id; diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index 3986b16ce3..acf2ce4311 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -41,6 +41,7 @@ impl merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, + _payment_confirm_source: Option, ) -> RouterResult> { let db = &*state.store; let merchant_id = &merchant_account.merchant_id; diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index adc137403e..b4f538e1d0 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -44,6 +44,7 @@ impl merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, + _payment_confirm_source: Option, ) -> RouterResult> { let db = &*state.store; let merchant_id = &merchant_account.merchant_id; diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 14fc28d672..151078a605 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -56,6 +56,7 @@ impl merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, auth_flow: services::AuthFlow, + payment_confirm_source: Option, ) -> RouterResult> { let merchant_id = &merchant_account.merchant_id; let storage_scheme = merchant_account.storage_scheme; @@ -117,17 +118,32 @@ impl helpers::validate_customer_access(&payment_intent, auth_flow, request)?; - helpers::validate_payment_status_against_not_allowed_statuses( - &payment_intent.status, - &[ - storage_enums::IntentStatus::Cancelled, - storage_enums::IntentStatus::Succeeded, - storage_enums::IntentStatus::Processing, - storage_enums::IntentStatus::RequiresCapture, - storage_enums::IntentStatus::RequiresMerchantAction, - ], - "confirm", - )?; + if let Some(common_enums::PaymentSource::Webhook) = payment_confirm_source { + helpers::validate_payment_status_against_not_allowed_statuses( + &payment_intent.status, + &[ + storage_enums::IntentStatus::Cancelled, + storage_enums::IntentStatus::Succeeded, + storage_enums::IntentStatus::Processing, + storage_enums::IntentStatus::RequiresCapture, + storage_enums::IntentStatus::RequiresMerchantAction, + ], + "confirm", + )?; + } else { + helpers::validate_payment_status_against_not_allowed_statuses( + &payment_intent.status, + &[ + storage_enums::IntentStatus::Cancelled, + storage_enums::IntentStatus::Succeeded, + storage_enums::IntentStatus::Processing, + storage_enums::IntentStatus::RequiresCapture, + storage_enums::IntentStatus::RequiresMerchantAction, + storage_enums::IntentStatus::RequiresCustomerAction, + ], + "confirm", + )?; + } helpers::authenticate_client_secret(request.client_secret.as_ref(), &payment_intent)?; diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index d02ad15fbd..381f026597 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -59,6 +59,7 @@ impl merchant_account: &domain::MerchantAccount, merchant_key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, + _payment_confirm_source: Option, ) -> RouterResult> { let db = &*state.store; let ephemeral_key = Self::get_ephemeral_key(request, state, merchant_account).await; diff --git a/crates/router/src/core/payments/operations/payment_method_validate.rs b/crates/router/src/core/payments/operations/payment_method_validate.rs deleted file mode 100644 index 9ea347afd7..0000000000 --- a/crates/router/src/core/payments/operations/payment_method_validate.rs +++ /dev/null @@ -1,398 +0,0 @@ -use std::marker::PhantomData; - -use api_models::enums::FrmSuggestion; -use async_trait::async_trait; -use common_utils::{date_time, errors::CustomResult, ext_traits::AsyncExt}; -use error_stack::ResultExt; -use router_derive::PaymentOperation; -use router_env::{instrument, tracing}; - -use super::{BoxedOperation, Domain, GetTracker, UpdateTracker, ValidateRequest}; -use crate::{ - consts, - core::{ - errors::{self, RouterResult, StorageErrorExt}, - payment_methods::PaymentMethodRetrieve, - payments::{self, helpers, operations, Operation, PaymentData}, - utils as core_utils, - }, - db::StorageInterface, - routes::AppState, - services, - types::{ - self, - api::{self, enums as api_enums, PaymentIdTypeExt}, - domain, - storage::{self, enums as storage_enums}, - }, - utils, -}; - -#[derive(Debug, Clone, Copy, PaymentOperation)] -#[operation(operations = "all", flow = "verify")] -pub struct PaymentMethodValidate; - -impl ValidateRequest - for PaymentMethodValidate -{ - #[instrument(skip_all)] - fn validate_request<'a, 'b>( - &'b self, - request: &api::VerifyRequest, - merchant_account: &'a domain::MerchantAccount, - ) -> RouterResult<( - BoxedOperation<'b, F, api::VerifyRequest, Ctx>, - operations::ValidateResult<'a>, - )> { - let request_merchant_id = request.merchant_id.as_deref(); - helpers::validate_merchant_id(&merchant_account.merchant_id, request_merchant_id) - .change_context(errors::ApiErrorResponse::MerchantAccountNotFound)?; - - let mandate_type = - helpers::validate_mandate(request, payments::is_operation_confirm(self))?; - let validation_id = core_utils::get_or_generate_id("validation_id", &None, "val")?; - - Ok(( - Box::new(self), - operations::ValidateResult { - merchant_id: &merchant_account.merchant_id, - payment_id: api::PaymentIdType::PaymentIntentId(validation_id), - mandate_type, - storage_scheme: merchant_account.storage_scheme, - requeue: false, - }, - )) - } -} - -#[async_trait] -impl - GetTracker, api::VerifyRequest, Ctx> for PaymentMethodValidate -{ - #[instrument(skip_all)] - async fn get_trackers<'a>( - &'a self, - state: &'a AppState, - payment_id: &api::PaymentIdType, - request: &api::VerifyRequest, - _mandate_type: Option, - merchant_account: &domain::MerchantAccount, - _mechant_key_store: &domain::MerchantKeyStore, - _auth_flow: services::AuthFlow, - ) -> RouterResult<( - BoxedOperation<'a, F, api::VerifyRequest, Ctx>, - PaymentData, - Option, - )> { - let db = &*state.store; - - let merchant_id = &merchant_account.merchant_id; - let storage_scheme = merchant_account.storage_scheme; - - let (payment_intent, payment_attempt); - - let payment_id = payment_id - .get_payment_intent_id() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed while getting payment_intent_id from PaymentIdType")?; - - payment_attempt = match db - .insert_payment_attempt( - Self::make_payment_attempt( - &payment_id, - merchant_id, - request.payment_method, - request, - state, - merchant_account.storage_scheme, - ), - storage_scheme, - ) - .await - { - Ok(payment_attempt) => Ok(payment_attempt), - Err(err) => { - Err(err.change_context(errors::ApiErrorResponse::VerificationFailed { data: None })) - } - }?; - - payment_intent = match db - .insert_payment_intent( - Self::make_payment_intent( - &payment_id, - merchant_id, - request, - payment_attempt.attempt_id.clone(), - merchant_account.storage_scheme, - ), - storage_scheme, - ) - .await - { - Ok(payment_intent) => Ok(payment_intent), - Err(err) => { - Err(err.change_context(errors::ApiErrorResponse::VerificationFailed { data: None })) - } - }?; - - let creds_identifier = request - .merchant_connector_details - .as_ref() - .map(|mcd| mcd.creds_identifier.to_owned()); - request - .merchant_connector_details - .to_owned() - .async_map(|mcd| async { - helpers::insert_merchant_connector_creds_to_config( - db, - merchant_account.merchant_id.as_str(), - mcd, - ) - .await - }) - .await - .transpose()?; - - Ok(( - Box::new(self), - PaymentData { - flow: PhantomData, - payment_intent, - payment_attempt, - /// currency and amount are irrelevant in this scenario - currency: storage_enums::Currency::default(), - amount: api::Amount::Zero, - email: None, - mandate_id: None, - mandate_connector: None, - setup_mandate: request.mandate_data.clone().map(Into::into), - token: request.payment_token.clone(), - payment_method_data: request.payment_method_data.clone(), - confirm: Some(true), - address: types::PaymentAddress::default(), - force_sync: None, - refunds: vec![], - disputes: vec![], - attempts: None, - sessions_token: vec![], - card_cvc: None, - creds_identifier, - pm_token: None, - connector_customer_id: None, - recurring_mandate_payment_data: None, - ephemeral_key: None, - multiple_capture_data: None, - redirect_response: None, - surcharge_details: None, - frm_message: None, - payment_link_data: None, - frm_metadata: None, - }, - Some(payments::CustomerDetails { - customer_id: request.customer_id.clone(), - name: request.name.clone(), - email: request.email.clone(), - phone: request.phone.clone(), - phone_country_code: request.phone_country_code.clone(), - }), - )) - } -} - -#[async_trait] -impl UpdateTracker, api::VerifyRequest, Ctx> - for PaymentMethodValidate -{ - #[instrument(skip_all)] - async fn update_trackers<'b>( - &'b self, - state: &'b AppState, - mut payment_data: PaymentData, - _customer: Option, - storage_scheme: storage_enums::MerchantStorageScheme, - _updated_customer: Option, - _mechant_key_store: &domain::MerchantKeyStore, - _frm_suggestion: Option, - _header_payload: api::HeaderPayload, - ) -> RouterResult<( - BoxedOperation<'b, F, api::VerifyRequest, Ctx>, - PaymentData, - )> - where - F: 'b + Send, - { - // There is no fsm involved in this operation all the change of states must happen in a single request - let status = Some(storage_enums::IntentStatus::Processing); - - let customer_id = payment_data.payment_intent.customer_id.clone(); - - payment_data.payment_intent = state - .store - .update_payment_intent( - payment_data.payment_intent, - storage::PaymentIntentUpdate::ReturnUrlUpdate { - return_url: None, - status, - customer_id, - shipping_address_id: None, - billing_address_id: None, - updated_by: storage_scheme.to_string(), - }, - storage_scheme, - ) - .await - .to_not_found_response(errors::ApiErrorResponse::VerificationFailed { data: None })?; - - Ok((Box::new(self), payment_data)) - } -} - -#[async_trait] -impl Domain for Op -where - F: Clone + Send, - Op: Send + Sync + Operation, - for<'a> &'a Op: Operation, -{ - #[instrument(skip_all)] - async fn get_or_create_customer_details<'a>( - &'a self, - db: &dyn StorageInterface, - payment_data: &mut PaymentData, - request: Option, - key_store: &domain::MerchantKeyStore, - ) -> CustomResult< - ( - BoxedOperation<'a, F, api::VerifyRequest, Ctx>, - Option, - ), - errors::StorageError, - > { - helpers::create_customer_if_not_exist( - Box::new(self), - db, - payment_data, - request, - &key_store.merchant_id, - key_store, - ) - .await - } - - #[instrument(skip_all)] - async fn make_pm_data<'a>( - &'a self, - state: &'a AppState, - payment_data: &mut PaymentData, - _storage_scheme: storage_enums::MerchantStorageScheme, - merchant_key_store: &domain::MerchantKeyStore, - ) -> RouterResult<( - BoxedOperation<'a, F, api::VerifyRequest, Ctx>, - Option, - )> { - helpers::make_pm_data(Box::new(self), state, payment_data, merchant_key_store).await - } - - async fn get_connector<'a>( - &'a self, - _merchant_account: &domain::MerchantAccount, - state: &AppState, - _request: &api::VerifyRequest, - _payment_intent: &storage::PaymentIntent, - _mechant_key_store: &domain::MerchantKeyStore, - ) -> CustomResult { - helpers::get_connector_default(state, None).await - } -} - -impl PaymentMethodValidate { - #[instrument(skip_all)] - fn make_payment_attempt( - payment_id: &str, - merchant_id: &str, - payment_method: Option, - _request: &api::VerifyRequest, - state: &AppState, - storage_scheme: storage_enums::MerchantStorageScheme, - ) -> storage::PaymentAttemptNew { - let created_at @ modified_at @ last_synced = Some(date_time::now()); - let status = storage_enums::AttemptStatus::Pending; - let attempt_id = if core_utils::is_merchant_enabled_for_payment_id_as_connector_request_id( - &state.conf, - merchant_id, - ) { - payment_id.to_string() - } else { - utils::get_payment_attempt_id(payment_id, 1) - }; - - storage::PaymentAttemptNew { - payment_id: payment_id.to_string(), - merchant_id: merchant_id.to_string(), - attempt_id, - status, - // Amount & Currency will be zero in this case - amount: 0, - currency: Default::default(), - connector: None, - payment_method, - confirm: true, - created_at, - modified_at, - last_synced, - updated_by: storage_scheme.to_string(), - ..Default::default() - } - } - - fn make_payment_intent( - payment_id: &str, - merchant_id: &str, - request: &api::VerifyRequest, - active_attempt_id: String, - storage_scheme: storage_enums::MerchantStorageScheme, - ) -> storage::PaymentIntentNew { - let created_at @ modified_at @ last_synced = Some(date_time::now()); - let status = helpers::payment_intent_status_fsm(&request.payment_method_data, Some(true)); - - let client_secret = - utils::generate_id(consts::ID_LENGTH, format!("{payment_id}_secret").as_str()); - storage::PaymentIntentNew { - payment_id: payment_id.to_string(), - merchant_id: merchant_id.to_string(), - status, - amount: 0, - currency: Default::default(), - connector_id: None, - created_at, - modified_at, - last_synced, - client_secret: Some(client_secret), - setup_future_usage: request.setup_future_usage, - off_session: request.off_session, - active_attempt: data_models::RemoteStorageObject::ForeignID(active_attempt_id), - attempt_count: 1, - amount_captured: Default::default(), - customer_id: Default::default(), - description: Default::default(), - return_url: Default::default(), - metadata: Default::default(), - shipping_address_id: Default::default(), - billing_address_id: Default::default(), - statement_descriptor_name: Default::default(), - statement_descriptor_suffix: Default::default(), - business_country: Default::default(), - business_label: Default::default(), - order_details: Default::default(), - allowed_payment_method_types: Default::default(), - connector_metadata: Default::default(), - feature_metadata: Default::default(), - profile_id: Default::default(), - merchant_decision: Default::default(), - payment_confirm_source: Default::default(), - surcharge_applicable: Default::default(), - payment_link_id: Default::default(), - updated_by: storage_scheme.to_string(), - } - } -} diff --git a/crates/router/src/core/payments/operations/payment_reject.rs b/crates/router/src/core/payments/operations/payment_reject.rs index e958422d31..02d2e953c1 100644 --- a/crates/router/src/core/payments/operations/payment_reject.rs +++ b/crates/router/src/core/payments/operations/payment_reject.rs @@ -41,6 +41,7 @@ impl merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, + _payment_confirm_source: Option, ) -> RouterResult> { let db = &*state.store; let merchant_id = &merchant_account.merchant_id; diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index 9a58dd5af7..170db39388 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -43,6 +43,7 @@ impl merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, + _payment_confirm_source: Option, ) -> RouterResult> { let payment_id = payment_id .get_payment_intent_id() diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index 557c5c0bd8..8b4ec4e3f5 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -42,6 +42,7 @@ impl merchant_account: &domain::MerchantAccount, mechant_key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, + _payment_confirm_source: Option, ) -> RouterResult> { let (mut payment_intent, payment_attempt, currency, amount); let db = &*state.store; diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index c6d9a30f0c..9db9742ca9 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -198,6 +198,7 @@ impl merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, + _payment_confirm_source: Option, ) -> RouterResult> { get_tracker_for_sync( diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index 015ef5cea6..664fce820a 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -44,6 +44,7 @@ impl merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, auth_flow: services::AuthFlow, + _payment_confirm_source: Option, ) -> RouterResult> { let (mut payment_intent, mut payment_attempt, currency): (_, _, storage_enums::Currency); diff --git a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs index 51fdff77c3..e7bb5622b7 100644 --- a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs +++ b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs @@ -46,6 +46,7 @@ impl merchant_account: &domain::MerchantAccount, _key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, + _payment_confirm_source: Option, ) -> RouterResult< operations::GetTrackerResponse<'a, F, PaymentsIncrementalAuthorizationRequest, Ctx>, > { diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index f291d1cd2e..f0348e45eb 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -603,7 +603,7 @@ async fn bank_transfer_webhook_flow