feat(router): add retrieve poll status api (#4358)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Sai Harsha Vardhan
2024-04-17 15:50:53 +05:30
committed by GitHub
parent 5a0a8cee76
commit ca47ea9b13
21 changed files with 254 additions and 6 deletions

View File

@ -25,6 +25,7 @@ pub mod payments;
#[cfg(feature = "payouts")] #[cfg(feature = "payouts")]
pub mod payouts; pub mod payouts;
pub mod pm_auth; pub mod pm_auth;
pub mod poll;
#[cfg(feature = "recon")] #[cfg(feature = "recon")]
pub mod recon; pub mod recon;
pub mod refunds; pub mod refunds;

View File

@ -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<ApiEventsType> {
Some(ApiEventsType::Poll {
poll_id: self.poll_id.clone(),
})
}
}

View File

@ -56,6 +56,9 @@ pub enum ApiEventsType {
Events { Events {
merchant_id_or_profile_id: String, merchant_id_or_profile_id: String,
}, },
Poll {
poll_id: String,
},
} }
impl ApiEventMetric for serde_json::Value {} impl ApiEventMetric for serde_json::Value {}

View File

@ -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_initial_webhook_delivery_attempts,
routes::webhook_events::list_webhook_delivery_attempts, routes::webhook_events::list_webhook_delivery_attempts,
routes::webhook_events::retry_webhook_delivery_attempt, routes::webhook_events::retry_webhook_delivery_attempt,
// Routes for poll apis
routes::poll::retrieve_poll_status,
), ),
components(schemas( components(schemas(
api_models::refunds::RefundRequest, 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::CardDetail,
api_models::payment_methods::CardDetailUpdate, api_models::payment_methods::CardDetailUpdate,
api_models::payment_methods::RequestPaymentMethodTypes, api_models::payment_methods::RequestPaymentMethodTypes,
api_models::poll::PollResponse,
api_models::poll::PollStatus,
api_models::customers::CustomerResponse, api_models::customers::CustomerResponse,
api_models::admin::AcceptedCountries, api_models::admin::AcceptedCountries,
api_models::admin::AcceptedCurrencies, api_models::admin::AcceptedCurrencies,

View File

@ -13,11 +13,12 @@ pub mod payment_link;
pub mod payment_method; pub mod payment_method;
pub mod payments; pub mod payments;
pub mod payouts; pub mod payouts;
pub mod poll;
pub mod refunds; pub mod refunds;
pub mod routing; pub mod routing;
pub mod webhook_events; pub mod webhook_events;
pub use self::{ pub use self::{
customers::*, mandates::*, merchant_account::*, merchant_connector_account::*, customers::*, mandates::*, merchant_account::*, merchant_connector_account::*,
payment_method::*, payments::*, refunds::*, routing::*, webhook_events::*, payment_method::*, payments::*, poll::*, refunds::*, routing::*, webhook_events::*,
}; };

View File

@ -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() {}

View File

@ -599,6 +599,10 @@ impl From<errors::ApiErrorResponse> for StripeErrorCode {
object: "business_profile".to_owned(), object: "business_profile".to_owned(),
id, id,
}, },
errors::ApiErrorResponse::PollNotFound { id } => Self::ResourceMissing {
object: "poll".to_owned(),
id,
},
errors::ApiErrorResponse::DisputeStatusValidationFailed { reason } => { errors::ApiErrorResponse::DisputeStatusValidationFailed { reason } => {
Self::InternalServerError Self::InternalServerError
} }

View File

@ -28,6 +28,7 @@ pub mod payments;
#[cfg(feature = "payouts")] #[cfg(feature = "payouts")]
pub mod payouts; pub mod payouts;
pub mod pm_auth; pub mod pm_auth;
pub mod poll;
pub mod refunds; pub mod refunds;
pub mod routing; pub mod routing;
pub mod surcharge_decision_config; pub mod surcharge_decision_config;

View File

@ -166,6 +166,8 @@ pub enum ApiErrorResponse {
MerchantConnectorAccountNotFound { id: String }, 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")] #[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 }, 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")] #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Resource ID does not exist in our records")]
ResourceIdNotFound, ResourceIdNotFound,
#[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Mandate does not exist in our records")] #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Mandate does not exist in our records")]

View File

@ -238,6 +238,9 @@ impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> for ApiErrorRespon
Self::FileNotFound => { Self::FileNotFound => {
AER::NotFound(ApiError::new("HE", 2, "File does not exist in our records", None)) 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 => { Self::FileNotAvailable => {
AER::NotFound(ApiError::new("HE", 2, "File not available", None)) AER::NotFound(ApiError::new("HE", 2, "File not available", None))
} }

View File

@ -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<PollResponse> {
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::<Option<String>>(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))
}

View File

@ -20,7 +20,7 @@ use crate::{
PaymentLinkFormData, PaymentLinkFormData,
}, },
types::api::{ 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<ApiEventsType> {
Some(ApiEventsType::Poll {
poll_id: self.poll_id.clone(),
})
}
}

View File

@ -125,6 +125,7 @@ pub fn mk_app(
.service(routes::EphemeralKey::server(state.clone())) .service(routes::EphemeralKey::server(state.clone()))
.service(routes::Webhooks::server(state.clone())) .service(routes::Webhooks::server(state.clone()))
.service(routes::PaymentMethods::server(state.clone())) .service(routes::PaymentMethods::server(state.clone()))
.service(routes::Poll::server(state.clone()))
} }
#[cfg(feature = "olap")] #[cfg(feature = "olap")]

View File

@ -31,6 +31,7 @@ pub mod payments;
pub mod payouts; pub mod payouts;
#[cfg(any(feature = "olap", feature = "oltp"))] #[cfg(any(feature = "olap", feature = "oltp"))]
pub mod pm_auth; pub mod pm_auth;
pub mod poll;
#[cfg(feature = "recon")] #[cfg(feature = "recon")]
pub mod recon; pub mod recon;
pub mod refunds; pub mod refunds;
@ -59,7 +60,7 @@ pub use self::app::Recon;
pub use self::app::{ pub use self::app::{
ApiKeys, AppState, BusinessProfile, Cache, Cards, Configs, ConnectorOnboarding, Customers, ApiKeys, AppState, BusinessProfile, Cache, Cards, Configs, ConnectorOnboarding, Customers,
Disputes, EphemeralKey, Files, Gsm, Health, Mandates, MerchantAccount, 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")] #[cfg(feature = "olap")]
pub use self::app::{Blocklist, Routing, Verify, WebhookEvents}; pub use self::app::{Blocklist, Routing, Verify, WebhookEvents};

View File

@ -23,8 +23,6 @@ use super::blocklist;
use super::dummy_connector::*; use super::dummy_connector::*;
#[cfg(feature = "payouts")] #[cfg(feature = "payouts")]
use super::payouts::*; use super::payouts::*;
#[cfg(feature = "oltp")]
use super::pm_auth;
#[cfg(feature = "olap")] #[cfg(feature = "olap")]
use super::routing as cloud_routing; use super::routing as cloud_routing;
#[cfg(feature = "olap")] #[cfg(feature = "olap")]
@ -41,6 +39,8 @@ use super::{configs::*, customers::*, mandates::*, payments::*, refunds::*};
use super::{currency, payment_methods::*}; use super::{currency, payment_methods::*};
#[cfg(feature = "oltp")] #[cfg(feature = "oltp")]
use super::{ephemeral_key::*, webhooks::*}; use super::{ephemeral_key::*, webhooks::*};
#[cfg(feature = "oltp")]
use super::{pm_auth, poll::retrieve_poll_status};
use crate::configs::secrets_transformers; use crate::configs::secrets_transformers;
#[cfg(all(feature = "frm", feature = "oltp"))] #[cfg(all(feature = "frm", feature = "oltp"))]
use crate::routes::fraud_check as frm_routes; 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; pub struct ApiKeys;
#[cfg(feature = "olap")] #[cfg(feature = "olap")]

View File

@ -34,6 +34,7 @@ pub enum ApiIdentifier {
UserRole, UserRole,
ConnectorOnboarding, ConnectorOnboarding,
Recon, Recon,
Poll,
} }
impl From<Flow> for ApiIdentifier { impl From<Flow> for ApiIdentifier {
@ -228,6 +229,8 @@ impl From<Flow> for ApiIdentifier {
| Flow::ReconServiceRequest | Flow::ReconServiceRequest
| Flow::ReconVerifyToken => Self::Recon, | Flow::ReconVerifyToken => Self::Recon,
Flow::CreateConnectorAgnosticMandateConfig => Self::Routing, Flow::CreateConnectorAgnosticMandateConfig => Self::Routing,
Flow::RetrievePollStatus => Self::Poll,
} }
} }
} }

View File

@ -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<AppState>,
req: HttpRequest,
path: web::Path<String>,
) -> 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
}

View File

@ -17,6 +17,7 @@ pub mod payment_methods;
pub mod payments; pub mod payments;
#[cfg(feature = "payouts")] #[cfg(feature = "payouts")]
pub mod payouts; pub mod payouts;
pub mod poll;
pub mod refunds; pub mod refunds;
pub mod routing; pub mod routing;
#[cfg(feature = "olap")] #[cfg(feature = "olap")]
@ -35,7 +36,7 @@ pub use self::fraud_check::*;
pub use self::payouts::*; pub use self::payouts::*;
pub use self::{ pub use self::{
admin::*, api_keys::*, authentication::*, configs::*, customers::*, disputes::*, files::*, 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 super::ErrorResponse;
use crate::{ use crate::{

View File

@ -0,0 +1,6 @@
use serde;
#[derive(Default, Debug, serde::Deserialize, serde::Serialize)]
pub struct PollId {
pub poll_id: String,
}

View File

@ -402,6 +402,8 @@ pub enum Flow {
WebhookEventDeliveryAttemptList, WebhookEventDeliveryAttemptList,
/// Manually retry the delivery for a webhook event /// Manually retry the delivery for a webhook event
WebhookEventDeliveryRetry, WebhookEventDeliveryRetry,
/// Retrieve status of the Poll
RetrievePollStatus,
} }
/// ///

View File

@ -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": { "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": { "PrimaryBusinessDetails": {
"type": "object", "type": "object",
"required": [ "required": [