From ca47ea9b13ff29085f7cc4e408f2b6498b1d6e8a Mon Sep 17 00:00:00 2001 From: Sai Harsha Vardhan <56996463+sai-harsha-vardhan@users.noreply.github.com> Date: Wed, 17 Apr 2024 15:50:53 +0530 Subject: [PATCH] feat(router): add retrieve poll status api (#4358) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/api_models/src/lib.rs | 1 + crates/api_models/src/poll.rs | 28 +++++++++ crates/common_utils/src/events.rs | 3 + crates/openapi/src/openapi.rs | 5 ++ crates/openapi/src/routes.rs | 3 +- crates/openapi/src/routes/poll.rs | 15 +++++ .../router/src/compatibility/stripe/errors.rs | 4 ++ crates/router/src/core.rs | 1 + .../src/core/errors/api_error_response.rs | 2 + crates/router/src/core/errors/transformers.rs | 3 + crates/router/src/core/poll.rs | 45 ++++++++++++++ crates/router/src/events/api_logs.rs | 10 ++- crates/router/src/lib.rs | 1 + crates/router/src/routes.rs | 3 +- crates/router/src/routes/app.rs | 15 ++++- crates/router/src/routes/lock_utils.rs | 3 + crates/router/src/routes/poll.rs | 45 ++++++++++++++ crates/router/src/types/api.rs | 3 +- crates/router/src/types/api/poll.rs | 6 ++ crates/router_env/src/logger/types.rs | 2 + openapi/openapi_spec.json | 62 +++++++++++++++++++ 21 files changed, 254 insertions(+), 6 deletions(-) create mode 100644 crates/api_models/src/poll.rs create mode 100644 crates/openapi/src/routes/poll.rs create mode 100644 crates/router/src/core/poll.rs create mode 100644 crates/router/src/routes/poll.rs create mode 100644 crates/router/src/types/api/poll.rs diff --git a/crates/api_models/src/lib.rs b/crates/api_models/src/lib.rs index 89097705a4..a0bc6f6362 100644 --- a/crates/api_models/src/lib.rs +++ b/crates/api_models/src/lib.rs @@ -25,6 +25,7 @@ pub mod payments; #[cfg(feature = "payouts")] pub mod payouts; pub mod pm_auth; +pub mod poll; #[cfg(feature = "recon")] pub mod recon; pub mod refunds; diff --git a/crates/api_models/src/poll.rs b/crates/api_models/src/poll.rs new file mode 100644 index 0000000000..f75754d72b --- /dev/null +++ b/crates/api_models/src/poll.rs @@ -0,0 +1,28 @@ +use common_utils::events::{ApiEventMetric, ApiEventsType}; +use serde::Serialize; +use utoipa::ToSchema; + +#[derive(Debug, ToSchema, Clone, Serialize)] +pub struct PollResponse { + /// The poll id + pub poll_id: String, + /// Status of the poll + pub status: PollStatus, +} + +#[derive(Debug, strum::Display, strum::EnumString, Clone, serde::Serialize, ToSchema)] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum PollStatus { + Pending, + Completed, + NotFound, +} + +impl ApiEventMetric for PollResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Poll { + poll_id: self.poll_id.clone(), + }) + } +} diff --git a/crates/common_utils/src/events.rs b/crates/common_utils/src/events.rs index 4e4a09d938..c51749cf20 100644 --- a/crates/common_utils/src/events.rs +++ b/crates/common_utils/src/events.rs @@ -56,6 +56,9 @@ pub enum ApiEventsType { Events { merchant_id_or_profile_id: String, }, + Poll { + poll_id: String, + }, } impl ApiEventMetric for serde_json::Value {} diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 97927d5707..972dde7542 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -178,6 +178,9 @@ Never share your secret api keys. Keep them guarded and secure. routes::webhook_events::list_initial_webhook_delivery_attempts, routes::webhook_events::list_webhook_delivery_attempts, routes::webhook_events::retry_webhook_delivery_attempt, + + // Routes for poll apis + routes::poll::retrieve_poll_status, ), components(schemas( api_models::refunds::RefundRequest, @@ -206,6 +209,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payment_methods::CardDetail, api_models::payment_methods::CardDetailUpdate, api_models::payment_methods::RequestPaymentMethodTypes, + api_models::poll::PollResponse, + api_models::poll::PollStatus, api_models::customers::CustomerResponse, api_models::admin::AcceptedCountries, api_models::admin::AcceptedCurrencies, diff --git a/crates/openapi/src/routes.rs b/crates/openapi/src/routes.rs index bf6182e495..1df4e4e4f8 100644 --- a/crates/openapi/src/routes.rs +++ b/crates/openapi/src/routes.rs @@ -13,11 +13,12 @@ pub mod payment_link; pub mod payment_method; pub mod payments; pub mod payouts; +pub mod poll; pub mod refunds; pub mod routing; pub mod webhook_events; pub use self::{ customers::*, mandates::*, merchant_account::*, merchant_connector_account::*, - payment_method::*, payments::*, refunds::*, routing::*, webhook_events::*, + payment_method::*, payments::*, poll::*, refunds::*, routing::*, webhook_events::*, }; diff --git a/crates/openapi/src/routes/poll.rs b/crates/openapi/src/routes/poll.rs new file mode 100644 index 0000000000..3ee725c3d2 --- /dev/null +++ b/crates/openapi/src/routes/poll.rs @@ -0,0 +1,15 @@ +/// Poll - Retrieve Poll Status +#[utoipa::path( + get, + path = "/poll/status/{poll_id}", + params( + ("poll_id" = String, Path, description = "The identifier for poll") + ), + responses( + (status = 200, description = "The poll status was retrieved successfully", body = PollResponse) + ), + tag = "Poll", + operation_id = "Retrieve Poll Status", + security(("publishable_key" = [])) +)] +pub async fn retrieve_poll_status() {} diff --git a/crates/router/src/compatibility/stripe/errors.rs b/crates/router/src/compatibility/stripe/errors.rs index ffb7c0c6d9..580eb4a80b 100644 --- a/crates/router/src/compatibility/stripe/errors.rs +++ b/crates/router/src/compatibility/stripe/errors.rs @@ -599,6 +599,10 @@ impl From for StripeErrorCode { object: "business_profile".to_owned(), id, }, + errors::ApiErrorResponse::PollNotFound { id } => Self::ResourceMissing { + object: "poll".to_owned(), + id, + }, errors::ApiErrorResponse::DisputeStatusValidationFailed { reason } => { Self::InternalServerError } diff --git a/crates/router/src/core.rs b/crates/router/src/core.rs index 704ac31b73..02a5873429 100644 --- a/crates/router/src/core.rs +++ b/crates/router/src/core.rs @@ -28,6 +28,7 @@ pub mod payments; #[cfg(feature = "payouts")] pub mod payouts; pub mod pm_auth; +pub mod poll; pub mod refunds; pub mod routing; pub mod surcharge_decision_config; diff --git a/crates/router/src/core/errors/api_error_response.rs b/crates/router/src/core/errors/api_error_response.rs index 9e147053fe..5d1e527d6d 100644 --- a/crates/router/src/core/errors/api_error_response.rs +++ b/crates/router/src/core/errors/api_error_response.rs @@ -166,6 +166,8 @@ pub enum ApiErrorResponse { MerchantConnectorAccountNotFound { id: String }, #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Business profile with the given id '{id}' does not exist in our records")] BusinessProfileNotFound { id: String }, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Poll with the given id '{id}' does not exist in our records")] + PollNotFound { id: String }, #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Resource ID does not exist in our records")] ResourceIdNotFound, #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Mandate does not exist in our records")] diff --git a/crates/router/src/core/errors/transformers.rs b/crates/router/src/core/errors/transformers.rs index 57f383d651..1197e01cdf 100644 --- a/crates/router/src/core/errors/transformers.rs +++ b/crates/router/src/core/errors/transformers.rs @@ -238,6 +238,9 @@ impl ErrorSwitch for ApiErrorRespon Self::FileNotFound => { AER::NotFound(ApiError::new("HE", 2, "File does not exist in our records", None)) } + Self::PollNotFound { .. } => { + AER::NotFound(ApiError::new("HE", 2, "Poll does not exist in our records", None)) + }, Self::FileNotAvailable => { AER::NotFound(ApiError::new("HE", 2, "File not available", None)) } diff --git a/crates/router/src/core/poll.rs b/crates/router/src/core/poll.rs new file mode 100644 index 0000000000..a4a4fdb9f8 --- /dev/null +++ b/crates/router/src/core/poll.rs @@ -0,0 +1,45 @@ +use api_models::poll::PollResponse; +use common_utils::ext_traits::StringExt; +use error_stack::ResultExt; +use router_env::{instrument, tracing}; + +use super::errors; +use crate::{core::errors::RouterResponse, services::ApplicationResponse, types::domain, AppState}; + +#[instrument(skip_all)] +pub async fn retrieve_poll_status( + state: AppState, + req: crate::types::api::PollId, + merchant_account: domain::MerchantAccount, +) -> RouterResponse { + let redis_conn = state + .store + .get_redis_conn() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get redis connection")?; + let request_poll_id = req.poll_id; + // prepend 'poll_{merchant_id}_' to restrict access to only fetching Poll IDs, as this is a freely passed string in the request + let poll_id = format!("poll_{}_{}", merchant_account.merchant_id, request_poll_id); + let redis_value = redis_conn + .get_key::>(poll_id.as_str()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable_lazy(|| { + format!( + "Error while fetching the value for {} from redis", + poll_id.clone() + ) + })? + .ok_or(errors::ApiErrorResponse::PollNotFound { + id: request_poll_id.clone(), + })?; + let status = redis_value + .parse_enum("PollStatus") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error while parsing PollStatus")?; + let poll_response = PollResponse { + poll_id: request_poll_id, + status, + }; + Ok(ApplicationResponse::Json(poll_response)) +} diff --git a/crates/router/src/events/api_logs.rs b/crates/router/src/events/api_logs.rs index beb0334d70..ddf7a9db06 100644 --- a/crates/router/src/events/api_logs.rs +++ b/crates/router/src/events/api_logs.rs @@ -20,7 +20,7 @@ use crate::{ PaymentLinkFormData, }, types::api::{ - AttachEvidenceRequest, Config, ConfigUpdate, CreateFileRequest, DisputeId, FileId, + AttachEvidenceRequest, Config, ConfigUpdate, CreateFileRequest, DisputeId, FileId, PollId, }, }; @@ -150,3 +150,11 @@ impl ApiEventMetric for DisputeId { }) } } + +impl ApiEventMetric for PollId { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Poll { + poll_id: self.poll_id.clone(), + }) + } +} diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index a8d6e792f3..feffb7f1e8 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -125,6 +125,7 @@ pub fn mk_app( .service(routes::EphemeralKey::server(state.clone())) .service(routes::Webhooks::server(state.clone())) .service(routes::PaymentMethods::server(state.clone())) + .service(routes::Poll::server(state.clone())) } #[cfg(feature = "olap")] diff --git a/crates/router/src/routes.rs b/crates/router/src/routes.rs index bf335a5e71..cd216cb0e8 100644 --- a/crates/router/src/routes.rs +++ b/crates/router/src/routes.rs @@ -31,6 +31,7 @@ pub mod payments; pub mod payouts; #[cfg(any(feature = "olap", feature = "oltp"))] pub mod pm_auth; +pub mod poll; #[cfg(feature = "recon")] pub mod recon; pub mod refunds; @@ -59,7 +60,7 @@ pub use self::app::Recon; pub use self::app::{ ApiKeys, AppState, BusinessProfile, Cache, Cards, Configs, ConnectorOnboarding, Customers, Disputes, EphemeralKey, Files, Gsm, Health, Mandates, MerchantAccount, - MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments, Refunds, User, Webhooks, + MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments, Poll, Refunds, User, Webhooks, }; #[cfg(feature = "olap")] pub use self::app::{Blocklist, Routing, Verify, WebhookEvents}; diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 29f888baf8..2771ee4294 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -23,8 +23,6 @@ use super::blocklist; use super::dummy_connector::*; #[cfg(feature = "payouts")] use super::payouts::*; -#[cfg(feature = "oltp")] -use super::pm_auth; #[cfg(feature = "olap")] use super::routing as cloud_routing; #[cfg(feature = "olap")] @@ -41,6 +39,8 @@ use super::{configs::*, customers::*, mandates::*, payments::*, refunds::*}; use super::{currency, payment_methods::*}; #[cfg(feature = "oltp")] use super::{ephemeral_key::*, webhooks::*}; +#[cfg(feature = "oltp")] +use super::{pm_auth, poll::retrieve_poll_status}; use crate::configs::secrets_transformers; #[cfg(all(feature = "frm", feature = "oltp"))] use crate::routes::fraud_check as frm_routes; @@ -978,6 +978,17 @@ impl Configs { } } +pub struct Poll; + +#[cfg(feature = "oltp")] +impl Poll { + pub fn server(config: AppState) -> Scope { + web::scope("/poll") + .app_data(web::Data::new(config)) + .service(web::resource("/status/{poll_id}").route(web::get().to(retrieve_poll_status))) + } +} + pub struct ApiKeys; #[cfg(feature = "olap")] diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index d9887ac034..8e29b28fb6 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -34,6 +34,7 @@ pub enum ApiIdentifier { UserRole, ConnectorOnboarding, Recon, + Poll, } impl From for ApiIdentifier { @@ -228,6 +229,8 @@ impl From for ApiIdentifier { | Flow::ReconServiceRequest | Flow::ReconVerifyToken => Self::Recon, Flow::CreateConnectorAgnosticMandateConfig => Self::Routing, + + Flow::RetrievePollStatus => Self::Poll, } } } diff --git a/crates/router/src/routes/poll.rs b/crates/router/src/routes/poll.rs new file mode 100644 index 0000000000..5e1f53adf4 --- /dev/null +++ b/crates/router/src/routes/poll.rs @@ -0,0 +1,45 @@ +use actix_web::{web, HttpRequest, HttpResponse}; +use router_env::{instrument, tracing, Flow}; + +use super::app::AppState; +use crate::{ + core::{api_locking, poll}, + services::{api, authentication as auth}, + types::api::PollId, +}; + +/// Poll - Retrieve Poll Status +#[utoipa::path( + get, + path = "/poll/status/{poll_id}", + params( + ("poll_id" = String, Path, description = "The identifier for poll") + ), + responses( + (status = 200, description = "The poll status was retrieved successfully", body = PollResponse) + ), + tag = "Poll", + operation_id = "Retrieve Poll Status", + security(("publishable_key" = [])) +)] +#[instrument(skip_all, fields(flow = ?Flow::RetrievePollStatus))] +pub async fn retrieve_poll_status( + state: web::Data, + req: HttpRequest, + path: web::Path, +) -> HttpResponse { + let flow = Flow::RetrievePollStatus; + let poll_id = PollId { + poll_id: path.into_inner(), + }; + api::server_wrap( + flow, + state, + &req, + poll_id, + |state, auth, req, _| poll::retrieve_poll_status(state, req, auth.merchant_account), + &auth::PublishableKeyAuth, + api_locking::LockAction::NotApplicable, + ) + .await +} diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index d38b46c6a7..01f4db9ea9 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -17,6 +17,7 @@ pub mod payment_methods; pub mod payments; #[cfg(feature = "payouts")] pub mod payouts; +pub mod poll; pub mod refunds; pub mod routing; #[cfg(feature = "olap")] @@ -35,7 +36,7 @@ pub use self::fraud_check::*; pub use self::payouts::*; pub use self::{ admin::*, api_keys::*, authentication::*, configs::*, customers::*, disputes::*, files::*, - payment_link::*, payment_methods::*, payments::*, refunds::*, webhooks::*, + payment_link::*, payment_methods::*, payments::*, poll::*, refunds::*, webhooks::*, }; use super::ErrorResponse; use crate::{ diff --git a/crates/router/src/types/api/poll.rs b/crates/router/src/types/api/poll.rs new file mode 100644 index 0000000000..2722b72f67 --- /dev/null +++ b/crates/router/src/types/api/poll.rs @@ -0,0 +1,6 @@ +use serde; + +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +pub struct PollId { + pub poll_id: String, +} diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 240bf379f6..991db38635 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -402,6 +402,8 @@ pub enum Flow { WebhookEventDeliveryAttemptList, /// Manually retry the delivery for a webhook event WebhookEventDeliveryRetry, + /// Retrieve status of the Poll + RetrievePollStatus, } /// diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index ff090a3787..3d953f3f46 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -4516,6 +4516,44 @@ } ] } + }, + "/poll/status/{poll_id}": { + "get": { + "tags": [ + "Poll" + ], + "summary": "Poll - Retrieve Poll Status", + "description": "Poll - Retrieve Poll Status", + "operationId": "Retrieve Poll Status", + "parameters": [ + { + "name": "poll_id", + "in": "path", + "description": "The identifier for poll", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The poll status was retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PollResponse" + } + } + } + } + }, + "security": [ + { + "publishable_key": [] + } + ] + } } }, "components": { @@ -16229,6 +16267,30 @@ } } }, + "PollResponse": { + "type": "object", + "required": [ + "poll_id", + "status" + ], + "properties": { + "poll_id": { + "type": "string", + "description": "The poll id" + }, + "status": { + "$ref": "#/components/schemas/PollStatus" + } + } + }, + "PollStatus": { + "type": "string", + "enum": [ + "pending", + "completed", + "not_found" + ] + }, "PrimaryBusinessDetails": { "type": "object", "required": [