diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 3604a1d910..beb1ee1dad 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -3264,6 +3264,56 @@ } ] } + }, + "/v2/process_tracker/revenue_recovery_workflow/{revenue_recovery_id}": { + "get": { + "tags": [ + "Revenue Recovery" + ], + "summary": "Revenue Recovery - Retrieve", + "description": "Retrieve the Revenue Recovery Payment Info", + "operationId": "Retrieve Revenue Recovery Info", + "parameters": [ + { + "name": "recovery_recovery_id", + "in": "path", + "description": "The payment intent id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Revenue Recovery Info Retrieved Successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RevenueRecoveryResponse" + } + } + } + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Resource missing" + }, + "422": { + "description": "Unprocessable request" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "jwt_key": [] + } + ] + } } }, "components": { @@ -19057,6 +19107,17 @@ }, "additionalProperties": false }, + "ProcessTrackerStatus": { + "type": "string", + "enum": [ + "processing", + "new", + "pending", + "process_started", + "finish", + "review" + ] + }, "ProcessorPaymentToken": { "type": "object", "description": "Processor payment token for MIT payments where payment_method_data is not available", @@ -20560,6 +20621,39 @@ } } }, + "RevenueRecoveryResponse": { + "type": "object", + "required": [ + "id", + "status", + "business_status" + ], + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string", + "nullable": true + }, + "schedule_time_for_payment": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "schedule_time_for_psync": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "status": { + "$ref": "#/components/schemas/ProcessTrackerStatus" + }, + "business_status": { + "type": "string" + } + } + }, "RevokeApiKeyResponse": { "type": "object", "description": "The response body for revoking an API Key.", diff --git a/crates/api_models/src/events.rs b/crates/api_models/src/events.rs index 31b0c1d8dc..973bdea9ee 100644 --- a/crates/api_models/src/events.rs +++ b/crates/api_models/src/events.rs @@ -11,10 +11,11 @@ pub mod payouts; #[cfg(feature = "recon")] pub mod recon; pub mod refund; +#[cfg(feature = "v2")] +pub mod revenue_recovery; pub mod routing; pub mod user; pub mod user_role; - use common_utils::{ events::{ApiEventMetric, ApiEventsType}, impl_api_event_type, diff --git a/crates/api_models/src/events/revenue_recovery.rs b/crates/api_models/src/events/revenue_recovery.rs new file mode 100644 index 0000000000..8327dfdded --- /dev/null +++ b/crates/api_models/src/events/revenue_recovery.rs @@ -0,0 +1,14 @@ +use common_utils::events::{ApiEventMetric, ApiEventsType}; + +use crate::process_tracker::revenue_recovery::{RevenueRecoveryId, RevenueRecoveryResponse}; + +impl ApiEventMetric for RevenueRecoveryResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::ProcessTracker) + } +} +impl ApiEventMetric for RevenueRecoveryId { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::ProcessTracker) + } +} diff --git a/crates/api_models/src/lib.rs b/crates/api_models/src/lib.rs index c3cf1f1d25..171bcb6775 100644 --- a/crates/api_models/src/lib.rs +++ b/crates/api_models/src/lib.rs @@ -30,6 +30,7 @@ pub mod payments; pub mod payouts; pub mod pm_auth; pub mod poll; +pub mod process_tracker; #[cfg(feature = "recon")] pub mod recon; pub mod refunds; diff --git a/crates/api_models/src/process_tracker.rs b/crates/api_models/src/process_tracker.rs new file mode 100644 index 0000000000..9854933734 --- /dev/null +++ b/crates/api_models/src/process_tracker.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "v2")] +pub mod revenue_recovery; diff --git a/crates/api_models/src/process_tracker/revenue_recovery.rs b/crates/api_models/src/process_tracker/revenue_recovery.rs new file mode 100644 index 0000000000..7baeb547a6 --- /dev/null +++ b/crates/api_models/src/process_tracker/revenue_recovery.rs @@ -0,0 +1,21 @@ +use common_utils::id_type; +use serde::{Deserialize, Serialize}; +use time::PrimitiveDateTime; +use utoipa::ToSchema; + +use crate::enums; +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct RevenueRecoveryResponse { + pub id: String, + pub name: Option, + pub schedule_time_for_payment: Option, + pub schedule_time_for_psync: Option, + #[schema(value_type = ProcessTrackerStatus, example = "finish")] + pub status: enums::ProcessTrackerStatus, + pub business_status: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct RevenueRecoveryId { + pub revenue_recovery_id: id_type::GlobalPaymentId, +} diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 1777863768..2860e06d59 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -21,7 +21,8 @@ pub mod diesel_exports { DbDisputeStatus as DisputeStatus, DbFraudCheckStatus as FraudCheckStatus, DbFutureUsage as FutureUsage, DbIntentStatus as IntentStatus, DbMandateStatus as MandateStatus, DbPaymentMethodIssuerCode as PaymentMethodIssuerCode, - DbPaymentType as PaymentType, DbRefundStatus as RefundStatus, + DbPaymentType as PaymentType, DbProcessTrackerStatus as ProcessTrackerStatus, + DbRefundStatus as RefundStatus, DbRequestIncrementalAuthorization as RequestIncrementalAuthorization, DbScaExemptionType as ScaExemptionType, DbSuccessBasedRoutingConclusiveState as SuccessBasedRoutingConclusiveState, @@ -6980,6 +6981,7 @@ pub enum Resource { ReconReports, RunRecon, ReconConfig, + RevenueRecovery, } #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, serde::Serialize, Hash)] @@ -7819,6 +7821,60 @@ pub enum TriggeredBy { External, } +#[derive( + Clone, + Copy, + Debug, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumString, + ToSchema, +)] +#[router_derive::diesel_enum(storage_type = "db_enum")] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum ProcessTrackerStatus { + // Picked by the producer + Processing, + // State when the task is added + New, + // Send to retry + Pending, + // Picked by consumer + ProcessStarted, + // Finished by consumer + Finish, + // Review the task + Review, +} + +#[derive( + serde::Serialize, + serde::Deserialize, + Clone, + Copy, + Debug, + PartialEq, + Eq, + strum::EnumString, + strum::Display, +)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +#[strum(serialize_all = "SCREAMING_SNAKE_CASE")] +pub enum ProcessTrackerRunner { + PaymentsSyncWorkflow, + RefundWorkflowRouter, + DeleteTokenizeDataWorkflow, + ApiKeyExpiryWorkflow, + OutgoingWebhookRetryWorkflow, + AttachPayoutAccountWorkflow, + PaymentMethodStatusUpdateWorkflow, + PassiveRecoveryWorkflow, +} + #[derive(Debug)] pub enum CryptoPadding { PKCS7, diff --git a/crates/common_utils/src/events.rs b/crates/common_utils/src/events.rs index 556090858f..f7a068b8e4 100644 --- a/crates/common_utils/src/events.rs +++ b/crates/common_utils/src/events.rs @@ -121,6 +121,7 @@ pub enum ApiEventsType { PaymentMethodSession { payment_method_session_id: id_type::GlobalPaymentMethodSessionId, }, + ProcessTracker, } impl ApiEventMetric for serde_json::Value {} diff --git a/crates/common_utils/src/id_type/global_id/payment.rs b/crates/common_utils/src/id_type/global_id/payment.rs index c65fd06f06..5de836cd48 100644 --- a/crates/common_utils/src/id_type/global_id/payment.rs +++ b/crates/common_utils/src/id_type/global_id/payment.rs @@ -1,3 +1,4 @@ +use common_enums::enums; use error_stack::ResultExt; use crate::{errors, generate_id_with_default_len, generate_time_ordered_id_without_prefix, types}; @@ -31,6 +32,15 @@ impl GlobalPaymentId { pub fn generate_client_secret(&self) -> types::ClientSecret { types::ClientSecret::new(self.clone(), generate_time_ordered_id_without_prefix()) } + + /// Generate the id for revenue recovery Execute PT workflow + pub fn get_execute_revenue_recovery_id( + &self, + task: &str, + runner: enums::ProcessTrackerRunner, + ) -> String { + format!("{task}_{runner}_{}", self.get_string_repr()) + } } // TODO: refactor the macro to include this id use case as well @@ -67,6 +77,15 @@ impl GlobalAttemptId { pub fn get_string_repr(&self) -> &str { self.0.get_string_repr() } + + /// Generate the id for Revenue Recovery Psync PT workflow + pub fn get_psync_revenue_recovery_id( + &self, + task: &str, + runner: enums::ProcessTrackerRunner, + ) -> String { + format!("{runner}_{task}_{}", self.get_string_repr()) + } } impl TryFrom> for GlobalAttemptId { diff --git a/crates/diesel_models/src/enums.rs b/crates/diesel_models/src/enums.rs index 1697a7c062..16b9d0ac9f 100644 --- a/crates/diesel_models/src/enums.rs +++ b/crates/diesel_models/src/enums.rs @@ -78,35 +78,6 @@ pub enum EventObjectType { PayoutDetails, } -#[derive( - Clone, - Copy, - Debug, - Eq, - PartialEq, - serde::Deserialize, - serde::Serialize, - strum::Display, - strum::EnumString, -)] -#[diesel_enum(storage_type = "db_enum")] -#[serde(rename_all = "snake_case")] -#[strum(serialize_all = "snake_case")] -pub enum ProcessTrackerStatus { - // Picked by the producer - Processing, - // State when the task is added - New, - // Send to retry - Pending, - // Picked by consumer - ProcessStarted, - // Finished by consumer - Finish, - // Review the task - Review, -} - // Refund #[derive( Clone, diff --git a/crates/diesel_models/src/process_tracker.rs b/crates/diesel_models/src/process_tracker.rs index 294fb33c66..67a07d3ae7 100644 --- a/crates/diesel_models/src/process_tracker.rs +++ b/crates/diesel_models/src/process_tracker.rs @@ -1,4 +1,4 @@ -use common_enums::ApiVersion; +pub use common_enums::{enums::ProcessTrackerRunner, ApiVersion}; use common_utils::ext_traits::Encode; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use error_stack::ResultExt; @@ -196,30 +196,6 @@ impl From for ProcessTrackerUpdateInternal { } } -#[derive( - serde::Serialize, - serde::Deserialize, - Clone, - Copy, - Debug, - PartialEq, - Eq, - strum::EnumString, - strum::Display, -)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -#[strum(serialize_all = "SCREAMING_SNAKE_CASE")] -pub enum ProcessTrackerRunner { - PaymentsSyncWorkflow, - RefundWorkflowRouter, - DeleteTokenizeDataWorkflow, - ApiKeyExpiryWorkflow, - OutgoingWebhookRetryWorkflow, - AttachPayoutAccountWorkflow, - PaymentMethodStatusUpdateWorkflow, - PassiveRecoveryWorkflow, -} - #[cfg(test)] mod tests { #![allow(clippy::unwrap_used)] diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 6e5eb0038f..b127421bce 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -148,6 +148,9 @@ Never share your secret api keys. Keep them guarded and secure. //Routes for refunds routes::refunds::refunds_create, + + // Routes for Revenue Recovery flow under Process Tracker + routes::revenue_recovery::revenue_recovery_pt_retrieve_api ), components(schemas( common_utils::types::MinorUnit, @@ -708,6 +711,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payment_methods::PaymentMethodSessionConfirmRequest, api_models::payment_methods::PaymentMethodSessionResponse, api_models::payment_methods::AuthenticationDetails, + api_models::process_tracker::revenue_recovery::RevenueRecoveryResponse, + api_models::enums::ProcessTrackerStatus, routes::payments::ForceSync, )), modifiers(&SecurityAddon) diff --git a/crates/openapi/src/routes.rs b/crates/openapi/src/routes.rs index 4486df82b0..cbd215dccd 100644 --- a/crates/openapi/src/routes.rs +++ b/crates/openapi/src/routes.rs @@ -17,5 +17,6 @@ pub mod poll; pub mod profile; pub mod refunds; pub mod relay; +pub mod revenue_recovery; pub mod routing; pub mod webhook_events; diff --git a/crates/openapi/src/routes/revenue_recovery.rs b/crates/openapi/src/routes/revenue_recovery.rs new file mode 100644 index 0000000000..8e3b2aaf41 --- /dev/null +++ b/crates/openapi/src/routes/revenue_recovery.rs @@ -0,0 +1,22 @@ +#[cfg(feature = "v2")] +/// Revenue Recovery - Retrieve +/// +/// Retrieve the Revenue Recovery Payment Info +#[utoipa::path( + get, + path = "/v2/process_tracker/revenue_recovery_workflow/{revenue_recovery_id}", + params( + ("recovery_recovery_id" = String, Path, description = "The payment intent id"), + ), + responses( + (status = 200, description = "Revenue Recovery Info Retrieved Successfully", body = RevenueRecoveryResponse), + (status = 500, description = "Internal server error"), + (status = 404, description = "Resource missing"), + (status = 422, description = "Unprocessable request"), + (status = 403, description = "Forbidden"), + ), + tag = "Revenue Recovery", + operation_id = "Retrieve Revenue Recovery Info", + security(("jwt_key" = [])) +)] +pub async fn revenue_recovery_pt_retrieve_api() {} diff --git a/crates/router/src/bin/scheduler.rs b/crates/router/src/bin/scheduler.rs index a997d062bc..747cc1d4ad 100644 --- a/crates/router/src/bin/scheduler.rs +++ b/crates/router/src/bin/scheduler.rs @@ -321,9 +321,9 @@ impl ProcessTrackerWorkflows for WorkflowRunner { storage::ProcessTrackerRunner::PaymentMethodStatusUpdateWorkflow => Ok(Box::new( workflows::payment_method_status_update::PaymentMethodStatusUpdateWorkflow, )), - storage::ProcessTrackerRunner::PassiveRecoveryWorkflow => Ok(Box::new( - workflows::passive_churn_recovery_workflow::ExecutePcrWorkflow, - )), + storage::ProcessTrackerRunner::PassiveRecoveryWorkflow => { + Ok(Box::new(workflows::revenue_recovery::ExecutePcrWorkflow)) + } } }; diff --git a/crates/router/src/core.rs b/crates/router/src/core.rs index fa243d0269..83c1058b42 100644 --- a/crates/router/src/core.rs +++ b/crates/router/src/core.rs @@ -57,6 +57,6 @@ pub mod webhooks; pub mod unified_authentication_service; -#[cfg(feature = "v2")] -pub mod passive_churn_recovery; pub mod relay; +#[cfg(feature = "v2")] +pub mod revenue_recovery; diff --git a/crates/router/src/core/passive_churn_recovery.rs b/crates/router/src/core/revenue_recovery.rs similarity index 68% rename from crates/router/src/core/passive_churn_recovery.rs rename to crates/router/src/core/revenue_recovery.rs index eead6ad254..b5d5e7a6cd 100644 --- a/crates/router/src/core/passive_churn_recovery.rs +++ b/crates/router/src/core/revenue_recovery.rs @@ -1,33 +1,40 @@ pub mod transformers; pub mod types; -use api_models::payments::{PaymentRevenueRecoveryMetadata, PaymentsRetrieveRequest}; -use common_utils::{self, ext_traits::OptionExt, id_type, types::keymanager::KeyManagerState}; +use api_models::{payments::PaymentsRetrieveRequest, process_tracker::revenue_recovery}; +use common_utils::{ + self, + ext_traits::{OptionExt, ValueExt}, + id_type, + types::keymanager::KeyManagerState, +}; use diesel_models::process_tracker::business_status; use error_stack::{self, ResultExt}; use hyperswitch_domain_models::{ - behaviour::ReverseConversion, - errors::api_error_response, + api::ApplicationResponse, payments::{PaymentIntent, PaymentStatusData}, ApiModelToDieselModelConvertor, }; -use scheduler::errors; +use scheduler::errors as sch_errors; use crate::{ core::{ - errors::RouterResult, - passive_churn_recovery::types as pcr_types, + errors::{self, RouterResponse, RouterResult, StorageErrorExt}, payments::{self, operations::Operation}, + revenue_recovery::types as pcr_types, }, db::StorageInterface, logger, routes::{metrics, SessionState}, types::{ api, - storage::{self, passive_churn_recovery as pcr}, + storage::{self, revenue_recovery as pcr}, transformers::ForeignInto, }, }; +pub const EXECUTE_WORKFLOW: &str = "EXECUTE_WORKFLOW"; +pub const PSYNC_WORKFLOW: &str = "PSYNC_WORKFLOW"; + pub async fn perform_execute_payment( state: &SessionState, execute_task_process: &storage::ProcessTracker, @@ -35,7 +42,7 @@ pub async fn perform_execute_payment( pcr_data: &pcr::PcrPaymentData, _key_manager_state: &KeyManagerState, payment_intent: &PaymentIntent, -) -> Result<(), errors::ProcessTrackerError> { +) -> Result<(), sch_errors::ProcessTrackerError> { let db = &*state.store; let mut pcr_metadata = payment_intent @@ -79,9 +86,9 @@ pub async fn perform_execute_payment( pcr_types::Decision::Psync(attempt_status, attempt_id) => { // find if a psync task is already present - let task = "PSYNC_WORKFLOW"; + let task = PSYNC_WORKFLOW; let runner = storage::ProcessTrackerRunner::PassiveRecoveryWorkflow; - let process_tracker_id = format!("{runner}_{task}_{}", attempt_id.get_string_repr()); + let process_tracker_id = attempt_id.get_psync_revenue_recovery_id(task, runner); let psync_process = db.find_process_by_id(&process_tracker_id).await?; match psync_process { @@ -138,8 +145,8 @@ async fn insert_psync_pcr_task( payment_attempt_id: id_type::GlobalAttemptId, runner: storage::ProcessTrackerRunner, ) -> RouterResult { - let task = "PSYNC_WORKFLOW"; - let process_tracker_id = format!("{runner}_{task}_{}", payment_attempt_id.get_string_repr()); + let task = PSYNC_WORKFLOW; + let process_tracker_id = payment_attempt_id.get_psync_revenue_recovery_id(task, runner); let schedule_time = common_utils::date_time::now(); let psync_workflow_tracking_data = pcr::PcrWorkflowTrackingData { global_payment_id: payment_id, @@ -158,13 +165,13 @@ async fn insert_psync_pcr_task( schedule_time, hyperswitch_domain_models::consts::API_VERSION, ) - .change_context(api_error_response::ApiErrorResponse::InternalServerError) + .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to construct delete tokenized data process tracker task")?; let response = db .insert_process(process_tracker_entry) .await - .change_context(api_error_response::ApiErrorResponse::InternalServerError) + .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to construct delete tokenized data process tracker task")?; metrics::TASKS_ADDED_COUNT.add(1, router_env::metric_attributes!(("flow", "PsyncPcr"))); @@ -219,3 +226,57 @@ pub async fn call_psync_api( .await?; Ok(payment_data) } + +pub async fn retrieve_revenue_recovery_process_tracker( + state: SessionState, + id: id_type::GlobalPaymentId, +) -> RouterResponse { + let db = &*state.store; + let task = EXECUTE_WORKFLOW; + let runner = storage::ProcessTrackerRunner::PassiveRecoveryWorkflow; + let process_tracker_id = id.get_execute_revenue_recovery_id(task, runner); + + let process_tracker = db + .find_process_by_id(&process_tracker_id) + .await + .to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound) + .attach_printable("error retrieving the process tracker id")? + .get_required_value("Process Tracker") + .change_context(errors::ApiErrorResponse::GenericNotFoundError { + message: "Entry For the following id doesn't exists".to_owned(), + })?; + + let tracking_data = process_tracker + .tracking_data + .clone() + .parse_value::("PCRWorkflowTrackingData") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to deserialize Pcr Workflow Tracking Data")?; + + let psync_task = PSYNC_WORKFLOW; + + let process_tracker_id_for_psync = tracking_data + .payment_attempt_id + .get_psync_revenue_recovery_id(psync_task, runner); + + let process_tracker_for_psync = db + .find_process_by_id(&process_tracker_id_for_psync) + .await + .map_err(|e| { + logger::error!("Error while retreiving psync task : {:?}", e); + }) + .ok() + .flatten(); + + let schedule_time_for_psync = process_tracker_for_psync.and_then(|pt| pt.schedule_time); + + let response = revenue_recovery::RevenueRecoveryResponse { + id: process_tracker.id, + name: process_tracker.name, + schedule_time_for_payment: process_tracker.schedule_time, + schedule_time_for_psync, + status: process_tracker.status, + business_status: process_tracker.business_status, + }; + Ok(ApplicationResponse::Json(response)) +} diff --git a/crates/router/src/core/passive_churn_recovery/transformers.rs b/crates/router/src/core/revenue_recovery/transformers.rs similarity index 92% rename from crates/router/src/core/passive_churn_recovery/transformers.rs rename to crates/router/src/core/revenue_recovery/transformers.rs index ce47714353..7d8fc50eeb 100644 --- a/crates/router/src/core/passive_churn_recovery/transformers.rs +++ b/crates/router/src/core/revenue_recovery/transformers.rs @@ -1,8 +1,6 @@ use common_enums::AttemptStatus; -use crate::{ - core::passive_churn_recovery::types::PcrAttemptStatus, types::transformers::ForeignFrom, -}; +use crate::{core::revenue_recovery::types::PcrAttemptStatus, types::transformers::ForeignFrom}; impl ForeignFrom for PcrAttemptStatus { fn foreign_from(s: AttemptStatus) -> Self { diff --git a/crates/router/src/core/passive_churn_recovery/types.rs b/crates/router/src/core/revenue_recovery/types.rs similarity index 96% rename from crates/router/src/core/passive_churn_recovery/types.rs rename to crates/router/src/core/revenue_recovery/types.rs index 0f895b6f2e..445042bbea 100644 --- a/crates/router/src/core/passive_churn_recovery/types.rs +++ b/crates/router/src/core/revenue_recovery/types.rs @@ -19,14 +19,14 @@ use time::PrimitiveDateTime; use crate::{ core::{ errors::{self, RouterResult}, - passive_churn_recovery::{self as core_pcr}, payments::{self, operations::Operation}, + revenue_recovery::{self as core_pcr}, }, db::StorageInterface, logger, routes::SessionState, types::{api::payments as api_types, storage, transformers::ForeignInto}, - workflows::passive_churn_recovery_workflow::get_schedule_time_to_retry_mit_payments, + workflows::revenue_recovery::get_schedule_time_to_retry_mit_payments, }; type RecoveryResult = error_stack::Result; @@ -87,7 +87,7 @@ impl Decision { intent_status: enums::IntentStatus, called_connector: enums::PaymentConnectorTransmission, active_attempt_id: Option, - pcr_data: &storage::passive_churn_recovery::PcrPaymentData, + pcr_data: &storage::revenue_recovery::PcrPaymentData, payment_id: &id_type::GlobalPaymentId, ) -> RecoveryResult { Ok(match (intent_status, called_connector, active_attempt_id) { @@ -132,7 +132,7 @@ impl Action { merchant_id: &id_type::MerchantId, payment_intent: &PaymentIntent, process: &storage::ProcessTracker, - pcr_data: &storage::passive_churn_recovery::PcrPaymentData, + pcr_data: &storage::revenue_recovery::PcrPaymentData, revenue_recovery_metadata: &PaymentRevenueRecoveryMetadata, ) -> RecoveryResult { let db = &*state.store; @@ -171,7 +171,7 @@ impl Action { state: &SessionState, payment_intent: &PaymentIntent, execute_task_process: &storage::ProcessTracker, - pcr_data: &storage::passive_churn_recovery::PcrPaymentData, + pcr_data: &storage::revenue_recovery::PcrPaymentData, revenue_recovery_metadata: &mut PaymentRevenueRecoveryMetadata, ) -> Result<(), errors::ProcessTrackerError> { let db = &*state.store; @@ -290,7 +290,7 @@ impl Action { async fn call_proxy_api( state: &SessionState, payment_intent: &PaymentIntent, - pcr_data: &storage::passive_churn_recovery::PcrPaymentData, + pcr_data: &storage::revenue_recovery::PcrPaymentData, revenue_recovery: &PaymentRevenueRecoveryMetadata, ) -> RouterResult> { let operation = payments::operations::proxy_payments_intent::PaymentProxyIntent; @@ -349,7 +349,7 @@ async fn call_proxy_api( pub async fn update_payment_intent_api( state: &SessionState, global_payment_id: id_type::GlobalPaymentId, - pcr_data: &storage::passive_churn_recovery::PcrPaymentData, + pcr_data: &storage::revenue_recovery::PcrPaymentData, update_req: PaymentsUpdateIntentRequest, ) -> RouterResult> { // TODO : Use api handler instead of calling payments_intent_operation_core diff --git a/crates/router/src/core/webhooks/recovery_incoming.rs b/crates/router/src/core/webhooks/recovery_incoming.rs index 60e06ab078..a7a627b052 100644 --- a/crates/router/src/core/webhooks/recovery_incoming.rs +++ b/crates/router/src/core/webhooks/recovery_incoming.rs @@ -15,8 +15,8 @@ use crate::{ db::StorageInterface, routes::{app::ReqState, metrics, SessionState}, services::{self, connector_integration_interface}, - types::{api, domain, storage::passive_churn_recovery as storage_churn_recovery}, - workflows::passive_churn_recovery_workflow, + types::{api, domain, storage::revenue_recovery as storage_churn_recovery}, + workflows::revenue_recovery as revenue_recovery_flow, }; #[allow(clippy::too_many_arguments)] @@ -466,22 +466,21 @@ impl RevenueRecoveryAttempt { .and_then(|feature_metadata| feature_metadata.get_retry_count()) .unwrap_or(0); - let schedule_time = - passive_churn_recovery_workflow::get_schedule_time_to_retry_mit_payments( - db, - &merchant_id, - (total_retry_count + 1).into(), - ) - .await - .map_or_else( - || { - Err( - report!(errors::RevenueRecoveryError::ScheduleTimeFetchFailed) - .attach_printable("Failed to get schedule time for pcr workflow"), - ) - }, - Ok, // Simply returns `time` wrapped in `Ok` - )?; + let schedule_time = revenue_recovery_flow::get_schedule_time_to_retry_mit_payments( + db, + &merchant_id, + (total_retry_count + 1).into(), + ) + .await + .map_or_else( + || { + Err( + report!(errors::RevenueRecoveryError::ScheduleTimeFetchFailed) + .attach_printable("Failed to get schedule time for pcr workflow"), + ) + }, + Ok, // Simply returns `time` wrapped in `Ok` + )?; let payment_attempt_id = payment_attempt_id .ok_or(report!( diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index fc5e2b94d1..dc7cb1233f 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -202,6 +202,11 @@ pub fn mk_app( .service(routes::WebhookEvents::server(state.clone())) .service(routes::FeatureMatrix::server(state.clone())); } + + #[cfg(feature = "v2")] + { + server_app = server_app.service(routes::ProcessTracker::server(state.clone())); + } } #[cfg(all(feature = "payouts", feature = "v1"))] diff --git a/crates/router/src/routes.rs b/crates/router/src/routes.rs index 78d2b508a4..34feb63ea7 100644 --- a/crates/router/src/routes.rs +++ b/crates/router/src/routes.rs @@ -65,6 +65,9 @@ pub mod recovery_webhooks; pub mod relay; +#[cfg(feature = "olap")] +pub mod process_tracker; + #[cfg(feature = "dummy_connector")] pub use self::app::DummyConnector; #[cfg(feature = "v2")] @@ -75,7 +78,8 @@ pub use self::app::{ ApiKeys, AppState, ApplePayCertificatesMigration, Cache, Cards, Configs, ConnectorOnboarding, Customers, Disputes, EphemeralKey, FeatureMatrix, Files, Forex, Gsm, Health, Hypersense, Mandates, MerchantAccount, MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments, - Poll, Profile, ProfileNew, Refunds, Relay, RelayWebhooks, SessionState, User, Webhooks, + Poll, ProcessTracker, Profile, ProfileNew, Refunds, Relay, RelayWebhooks, SessionState, User, + Webhooks, }; #[cfg(feature = "olap")] pub use self::app::{Blocklist, Organization, Routing, Verify, WebhookEvents}; diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 574b30f300..2200567ddf 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -2496,3 +2496,19 @@ impl FeatureMatrix { .service(web::resource("").route(web::get().to(feature_matrix::fetch_feature_matrix))) } } + +#[cfg(feature = "olap")] +pub struct ProcessTracker; + +#[cfg(all(feature = "olap", feature = "v2"))] +impl ProcessTracker { + pub fn server(state: AppState) -> Scope { + use super::process_tracker::revenue_recovery; + web::scope("/v2/process_tracker/revenue_recovery_workflow") + .app_data(web::Data::new(state.clone())) + .service( + web::resource("/{revenue_recovery_id}") + .route(web::get().to(revenue_recovery::revenue_recovery_pt_retrieve_api)), + ) + } +} diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 1c3e23c2b0..028051aa81 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -42,6 +42,7 @@ pub enum ApiIdentifier { CardNetworkTokenization, Hypersense, PaymentMethodSession, + ProcessTracker, } impl From for ApiIdentifier { @@ -333,6 +334,8 @@ impl From for ApiIdentifier { | Flow::PaymentMethodSessionUpdateSavedPaymentMethod | Flow::PaymentMethodSessionDeleteSavedPaymentMethod | Flow::PaymentMethodSessionUpdate => Self::PaymentMethodSession, + + Flow::RevenueRecoveryRetrieve => Self::ProcessTracker, } } } diff --git a/crates/router/src/routes/process_tracker.rs b/crates/router/src/routes/process_tracker.rs new file mode 100644 index 0000000000..9854933734 --- /dev/null +++ b/crates/router/src/routes/process_tracker.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "v2")] +pub mod revenue_recovery; diff --git a/crates/router/src/routes/process_tracker/revenue_recovery.rs b/crates/router/src/routes/process_tracker/revenue_recovery.rs new file mode 100644 index 0000000000..b0deb62fa8 --- /dev/null +++ b/crates/router/src/routes/process_tracker/revenue_recovery.rs @@ -0,0 +1,39 @@ +use actix_web::{web, HttpRequest, HttpResponse}; +use api_models::process_tracker::revenue_recovery as revenue_recovery_api; +use router_env::Flow; + +use crate::{ + core::{api_locking, revenue_recovery}, + routes::AppState, + services::{api, authentication as auth, authorization::permissions::Permission}, +}; + +pub async fn revenue_recovery_pt_retrieve_api( + state: web::Data, + req: HttpRequest, + path: web::Path, +) -> HttpResponse { + let flow = Flow::RevenueRecoveryRetrieve; + let id = path.into_inner(); + let payload = revenue_recovery_api::RevenueRecoveryId { + revenue_recovery_id: id, + }; + + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, _: (), id, _| { + revenue_recovery::retrieve_revenue_recovery_process_tracker( + state, + id.revenue_recovery_id, + ) + }, + &auth::JWTAuth { + permission: Permission::ProfileRevenueRecoveryRead, + }, + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/services/authorization/permission_groups.rs b/crates/router/src/services/authorization/permission_groups.rs index 6333de5f19..2aa58e4d2d 100644 --- a/crates/router/src/services/authorization/permission_groups.rs +++ b/crates/router/src/services/authorization/permission_groups.rs @@ -167,11 +167,12 @@ pub static OPERATIONS: [Resource; 8] = [ pub static CONNECTORS: [Resource; 2] = [Resource::Connector, Resource::Account]; -pub static WORKFLOWS: [Resource; 4] = [ +pub static WORKFLOWS: [Resource; 5] = [ Resource::Routing, Resource::ThreeDsDecisionManager, Resource::SurchargeDecisionManager, Resource::Account, + Resource::RevenueRecovery, ]; pub static ANALYTICS: [Resource; 3] = [Resource::Analytics, Resource::Report, Resource::Account]; diff --git a/crates/router/src/services/authorization/permissions.rs b/crates/router/src/services/authorization/permissions.rs index 37c75de746..55a562a9f6 100644 --- a/crates/router/src/services/authorization/permissions.rs +++ b/crates/router/src/services/authorization/permissions.rs @@ -95,6 +95,10 @@ generate_permissions! { scopes: [Read, Write], entities: [Merchant] }, + RevenueRecovery: { + scopes: [Read], + entities: [Profile] + } ] } @@ -109,6 +113,7 @@ pub fn get_resource_name(resource: Resource, entity_type: EntityType) -> &'stati (Resource::ApiKey, _) => "Api Keys", (Resource::Connector, _) => "Payment Processors, Payout Processors, Fraud & Risk Managers", (Resource::Routing, _) => "Routing", + (Resource::RevenueRecovery, _) => "Revenue Recovery", (Resource::ThreeDsDecisionManager, _) => "3DS Decision Manager", (Resource::SurchargeDecisionManager, _) => "Surcharge Decision Manager", (Resource::Analytics, _) => "Analytics", diff --git a/crates/router/src/types/storage.rs b/crates/router/src/types/storage.rs index f14d69a098..341f9327f7 100644 --- a/crates/router/src/types/storage.rs +++ b/crates/router/src/types/storage.rs @@ -28,14 +28,14 @@ pub mod mandate; pub mod merchant_account; pub mod merchant_connector_account; pub mod merchant_key_store; -#[cfg(feature = "v2")] -pub mod passive_churn_recovery; pub mod payment_attempt; pub mod payment_link; pub mod payment_method; pub mod payout_attempt; pub mod payouts; pub mod refund; +#[cfg(feature = "v2")] +pub mod revenue_recovery; pub mod reverse_lookup; pub mod role; pub mod routing_algorithm; diff --git a/crates/router/src/types/storage/passive_churn_recovery.rs b/crates/router/src/types/storage/revenue_recovery.rs similarity index 100% rename from crates/router/src/types/storage/passive_churn_recovery.rs rename to crates/router/src/types/storage/revenue_recovery.rs diff --git a/crates/router/src/workflows.rs b/crates/router/src/workflows.rs index 4961932e06..b62a3b4c81 100644 --- a/crates/router/src/workflows.rs +++ b/crates/router/src/workflows.rs @@ -10,4 +10,4 @@ pub mod refund_router; pub mod tokenized_data; -pub mod passive_churn_recovery_workflow; +pub mod revenue_recovery; diff --git a/crates/router/src/workflows/passive_churn_recovery_workflow.rs b/crates/router/src/workflows/revenue_recovery.rs similarity index 98% rename from crates/router/src/workflows/passive_churn_recovery_workflow.rs rename to crates/router/src/workflows/revenue_recovery.rs index d817f3687c..f9092c58a8 100644 --- a/crates/router/src/workflows/passive_churn_recovery_workflow.rs +++ b/crates/router/src/workflows/revenue_recovery.rs @@ -15,14 +15,14 @@ use scheduler::{types::process_data, utils as scheduler_utils}; #[cfg(feature = "v2")] use crate::{ core::{ - passive_churn_recovery::{self as pcr}, payments, + revenue_recovery::{self as pcr}, }, db::StorageInterface, errors::StorageError, types::{ api::{self as api_types}, - storage::passive_churn_recovery as pcr_storage_types, + storage::revenue_recovery as pcr_storage_types, }, }; use crate::{routes::SessionState, types::storage}; diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 890dcae12c..1bf34bac6a 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -578,6 +578,8 @@ pub enum Flow { CardsInfoMigrate, ///Total payment method count for merchant TotalPaymentMethodCount, + /// Process Tracker Revenue Recovery Workflow Retrieve + RevenueRecoveryRetrieve, } /// Trait for providing generic behaviour to flow metric