mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 19:46:48 +08:00
fix(analytics): fix bugs in payments page metrics in Analytics V2 dashboard (#6654)
This commit is contained in:
@ -273,8 +273,14 @@ impl PaymentIntentMetricAccumulator for PaymentsDistributionAccumulator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) {
|
if status.as_ref() != &storage_enums::IntentStatus::RequiresCustomerAction
|
||||||
self.total += total;
|
&& status.as_ref() != &storage_enums::IntentStatus::RequiresPaymentMethod
|
||||||
|
&& status.as_ref() != &storage_enums::IntentStatus::RequiresMerchantAction
|
||||||
|
&& status.as_ref() != &storage_enums::IntentStatus::RequiresConfirmation
|
||||||
|
{
|
||||||
|
if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) {
|
||||||
|
self.total += total;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,10 +8,9 @@ use api_models::analytics::{
|
|||||||
},
|
},
|
||||||
GetPaymentIntentFiltersRequest, GetPaymentIntentMetricRequest, PaymentIntentFilterValue,
|
GetPaymentIntentFiltersRequest, GetPaymentIntentMetricRequest, PaymentIntentFilterValue,
|
||||||
PaymentIntentFiltersResponse, PaymentIntentsAnalyticsMetadata, PaymentIntentsMetricsResponse,
|
PaymentIntentFiltersResponse, PaymentIntentsAnalyticsMetadata, PaymentIntentsMetricsResponse,
|
||||||
SankeyResponse,
|
|
||||||
};
|
};
|
||||||
use bigdecimal::ToPrimitive;
|
use bigdecimal::ToPrimitive;
|
||||||
use common_enums::{Currency, IntentStatus};
|
use common_enums::Currency;
|
||||||
use common_utils::{errors::CustomResult, types::TimeRange};
|
use common_utils::{errors::CustomResult, types::TimeRange};
|
||||||
use currency_conversion::{conversion::convert, types::ExchangeRates};
|
use currency_conversion::{conversion::convert, types::ExchangeRates};
|
||||||
use error_stack::ResultExt;
|
use error_stack::ResultExt;
|
||||||
@ -24,7 +23,7 @@ use router_env::{
|
|||||||
use super::{
|
use super::{
|
||||||
filters::{get_payment_intent_filter_for_dimension, PaymentIntentFilterRow},
|
filters::{get_payment_intent_filter_for_dimension, PaymentIntentFilterRow},
|
||||||
metrics::PaymentIntentMetricRow,
|
metrics::PaymentIntentMetricRow,
|
||||||
sankey::{get_sankey_data, SessionizerRefundStatus},
|
sankey::{get_sankey_data, SankeyRow},
|
||||||
PaymentIntentMetricsAccumulator,
|
PaymentIntentMetricsAccumulator,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -51,7 +50,7 @@ pub async fn get_sankey(
|
|||||||
pool: &AnalyticsProvider,
|
pool: &AnalyticsProvider,
|
||||||
auth: &AuthInfo,
|
auth: &AuthInfo,
|
||||||
req: TimeRange,
|
req: TimeRange,
|
||||||
) -> AnalyticsResult<SankeyResponse> {
|
) -> AnalyticsResult<Vec<SankeyRow>> {
|
||||||
match pool {
|
match pool {
|
||||||
AnalyticsProvider::Sqlx(_) => Err(AnalyticsError::NotImplemented(
|
AnalyticsProvider::Sqlx(_) => Err(AnalyticsError::NotImplemented(
|
||||||
"Sankey not implemented for sqlx",
|
"Sankey not implemented for sqlx",
|
||||||
@ -62,69 +61,7 @@ pub async fn get_sankey(
|
|||||||
let sankey_rows = get_sankey_data(ckh_pool, auth, &req)
|
let sankey_rows = get_sankey_data(ckh_pool, auth, &req)
|
||||||
.await
|
.await
|
||||||
.change_context(AnalyticsError::UnknownError)?;
|
.change_context(AnalyticsError::UnknownError)?;
|
||||||
let mut sankey_response = SankeyResponse::default();
|
Ok(sankey_rows)
|
||||||
for i in sankey_rows {
|
|
||||||
match (
|
|
||||||
i.status.as_ref(),
|
|
||||||
i.refunds_status.unwrap_or_default().as_ref(),
|
|
||||||
i.attempt_count,
|
|
||||||
) {
|
|
||||||
(IntentStatus::Succeeded, SessionizerRefundStatus::FullRefunded, 1) => {
|
|
||||||
sankey_response.refunded += i.count;
|
|
||||||
sankey_response.normal_success += i.count
|
|
||||||
}
|
|
||||||
(IntentStatus::Succeeded, SessionizerRefundStatus::PartialRefunded, 1) => {
|
|
||||||
sankey_response.partial_refunded += i.count;
|
|
||||||
sankey_response.normal_success += i.count
|
|
||||||
}
|
|
||||||
(IntentStatus::Succeeded, SessionizerRefundStatus::FullRefunded, _) => {
|
|
||||||
sankey_response.refunded += i.count;
|
|
||||||
sankey_response.smart_retried_success += i.count
|
|
||||||
}
|
|
||||||
(IntentStatus::Succeeded, SessionizerRefundStatus::PartialRefunded, _) => {
|
|
||||||
sankey_response.partial_refunded += i.count;
|
|
||||||
sankey_response.smart_retried_success += i.count
|
|
||||||
}
|
|
||||||
(
|
|
||||||
IntentStatus::Succeeded
|
|
||||||
| IntentStatus::PartiallyCaptured
|
|
||||||
| IntentStatus::PartiallyCapturedAndCapturable
|
|
||||||
| IntentStatus::RequiresCapture,
|
|
||||||
SessionizerRefundStatus::NotRefunded,
|
|
||||||
1,
|
|
||||||
) => sankey_response.normal_success += i.count,
|
|
||||||
(
|
|
||||||
IntentStatus::Succeeded
|
|
||||||
| IntentStatus::PartiallyCaptured
|
|
||||||
| IntentStatus::PartiallyCapturedAndCapturable
|
|
||||||
| IntentStatus::RequiresCapture,
|
|
||||||
SessionizerRefundStatus::NotRefunded,
|
|
||||||
_,
|
|
||||||
) => sankey_response.smart_retried_success += i.count,
|
|
||||||
(IntentStatus::Failed, _, 1) => sankey_response.normal_failure += i.count,
|
|
||||||
(IntentStatus::Failed, _, _) => {
|
|
||||||
sankey_response.smart_retried_failure += i.count
|
|
||||||
}
|
|
||||||
(IntentStatus::Cancelled, _, _) => sankey_response.cancelled += i.count,
|
|
||||||
(IntentStatus::Processing, _, _) => sankey_response.pending += i.count,
|
|
||||||
(IntentStatus::RequiresCustomerAction, _, _) => {
|
|
||||||
sankey_response.customer_awaited += i.count
|
|
||||||
}
|
|
||||||
(IntentStatus::RequiresMerchantAction, _, _) => {
|
|
||||||
sankey_response.merchant_awaited += i.count
|
|
||||||
}
|
|
||||||
(IntentStatus::RequiresPaymentMethod, _, _) => {
|
|
||||||
sankey_response.pm_awaited += i.count
|
|
||||||
}
|
|
||||||
(IntentStatus::RequiresConfirmation, _, _) => {
|
|
||||||
sankey_response.confirmation_awaited += i.count
|
|
||||||
}
|
|
||||||
i @ (_, _, _) => {
|
|
||||||
router_env::logger::error!(status=?i, "Unknown status in sankey data");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(sankey_response)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ use common_utils::{
|
|||||||
};
|
};
|
||||||
use error_stack::ResultExt;
|
use error_stack::ResultExt;
|
||||||
use router_env::logger;
|
use router_env::logger;
|
||||||
use time::PrimitiveDateTime;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
clickhouse::ClickhouseClient,
|
clickhouse::ClickhouseClient,
|
||||||
@ -13,29 +12,19 @@ use crate::{
|
|||||||
types::{AnalyticsCollection, DBEnumWrapper, MetricsError, MetricsResult},
|
types::{AnalyticsCollection, DBEnumWrapper, MetricsError, MetricsResult},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, serde::Deserialize, Hash)]
|
|
||||||
pub struct PaymentIntentMetricRow {
|
|
||||||
pub profile_id: Option<String>,
|
|
||||||
pub connector: Option<String>,
|
|
||||||
pub authentication_type: Option<DBEnumWrapper<enums::AuthenticationType>>,
|
|
||||||
pub payment_method: Option<String>,
|
|
||||||
pub payment_method_type: Option<String>,
|
|
||||||
pub card_network: Option<String>,
|
|
||||||
pub merchant_id: Option<String>,
|
|
||||||
pub card_last_4: Option<String>,
|
|
||||||
pub card_issuer: Option<String>,
|
|
||||||
pub error_reason: Option<String>,
|
|
||||||
pub first_attempt: Option<i64>,
|
|
||||||
pub total: Option<bigdecimal::BigDecimal>,
|
|
||||||
pub count: Option<i64>,
|
|
||||||
#[serde(with = "common_utils::custom_serde::iso8601::option")]
|
|
||||||
pub start_bucket: Option<PrimitiveDateTime>,
|
|
||||||
#[serde(with = "common_utils::custom_serde::iso8601::option")]
|
|
||||||
pub end_bucket: Option<PrimitiveDateTime>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Default, serde::Deserialize, strum::AsRefStr, strum::EnumString, strum::Display,
|
Clone,
|
||||||
|
Copy,
|
||||||
|
Debug,
|
||||||
|
Default,
|
||||||
|
Eq,
|
||||||
|
Hash,
|
||||||
|
PartialEq,
|
||||||
|
serde::Deserialize,
|
||||||
|
serde::Serialize,
|
||||||
|
strum::Display,
|
||||||
|
strum::EnumIter,
|
||||||
|
strum::EnumString,
|
||||||
)]
|
)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum SessionizerRefundStatus {
|
pub enum SessionizerRefundStatus {
|
||||||
@ -45,13 +34,36 @@ pub enum SessionizerRefundStatus {
|
|||||||
PartialRefunded,
|
PartialRefunded,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize)]
|
#[derive(
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
Debug,
|
||||||
|
Default,
|
||||||
|
Eq,
|
||||||
|
Hash,
|
||||||
|
PartialEq,
|
||||||
|
serde::Deserialize,
|
||||||
|
serde::Serialize,
|
||||||
|
strum::Display,
|
||||||
|
strum::EnumIter,
|
||||||
|
strum::EnumString,
|
||||||
|
)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum SessionizerDisputeStatus {
|
||||||
|
DisputePresent,
|
||||||
|
#[default]
|
||||||
|
NotDisputed,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct SankeyRow {
|
pub struct SankeyRow {
|
||||||
|
pub count: i64,
|
||||||
pub status: DBEnumWrapper<enums::IntentStatus>,
|
pub status: DBEnumWrapper<enums::IntentStatus>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub refunds_status: Option<DBEnumWrapper<SessionizerRefundStatus>>,
|
pub refunds_status: Option<DBEnumWrapper<SessionizerRefundStatus>>,
|
||||||
pub attempt_count: i64,
|
#[serde(default)]
|
||||||
pub count: i64,
|
pub dispute_status: Option<DBEnumWrapper<SessionizerDisputeStatus>>,
|
||||||
|
pub first_attempt: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryInto<SankeyRow> for serde_json::Value {
|
impl TryInto<SankeyRow> for serde_json::Value {
|
||||||
@ -90,7 +102,12 @@ pub async fn get_sankey_data(
|
|||||||
.change_context(MetricsError::QueryBuildingError)?;
|
.change_context(MetricsError::QueryBuildingError)?;
|
||||||
|
|
||||||
query_builder
|
query_builder
|
||||||
.add_select_column("attempt_count")
|
.add_select_column("dispute_status")
|
||||||
|
.attach_printable("Error adding select clause")
|
||||||
|
.change_context(MetricsError::QueryBuildingError)?;
|
||||||
|
|
||||||
|
query_builder
|
||||||
|
.add_select_column("(attempt_count = 1) as first_attempt")
|
||||||
.attach_printable("Error adding select clause")
|
.attach_printable("Error adding select clause")
|
||||||
.change_context(MetricsError::QueryBuildingError)?;
|
.change_context(MetricsError::QueryBuildingError)?;
|
||||||
|
|
||||||
@ -112,7 +129,12 @@ pub async fn get_sankey_data(
|
|||||||
.change_context(MetricsError::QueryBuildingError)?;
|
.change_context(MetricsError::QueryBuildingError)?;
|
||||||
|
|
||||||
query_builder
|
query_builder
|
||||||
.add_group_by_clause("attempt_count")
|
.add_group_by_clause("dispute_status")
|
||||||
|
.attach_printable("Error adding group by clause")
|
||||||
|
.change_context(MetricsError::QueryBuildingError)?;
|
||||||
|
|
||||||
|
query_builder
|
||||||
|
.add_group_by_clause("first_attempt")
|
||||||
.attach_printable("Error adding group by clause")
|
.attach_printable("Error adding group by clause")
|
||||||
.change_context(MetricsError::QueryBuildingError)?;
|
.change_context(MetricsError::QueryBuildingError)?;
|
||||||
|
|
||||||
|
|||||||
@ -218,12 +218,19 @@ impl PaymentMetricAccumulator for PaymentsDistributionAccumulator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) {
|
if status.as_ref() != &storage_enums::AttemptStatus::AuthenticationFailed
|
||||||
self.total += total;
|
&& status.as_ref() != &storage_enums::AttemptStatus::PaymentMethodAwaited
|
||||||
if metrics.first_attempt.unwrap_or(false) {
|
&& status.as_ref() != &storage_enums::AttemptStatus::DeviceDataCollectionPending
|
||||||
self.total_without_retries += total;
|
&& status.as_ref() != &storage_enums::AttemptStatus::ConfirmationAwaited
|
||||||
} else {
|
&& status.as_ref() != &storage_enums::AttemptStatus::Unresolved
|
||||||
self.total_with_only_retries += total;
|
{
|
||||||
|
if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) {
|
||||||
|
self.total += total;
|
||||||
|
if metrics.first_attempt.unwrap_or(false) {
|
||||||
|
self.total_without_retries += total;
|
||||||
|
} else {
|
||||||
|
self.total_with_only_retries += total;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -160,11 +160,6 @@ where
|
|||||||
.switch()?;
|
.switch()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
outer_query_builder
|
|
||||||
.set_limit_by(5, &filtered_dimensions)
|
|
||||||
.attach_printable("Error adding limit clause")
|
|
||||||
.switch()?;
|
|
||||||
|
|
||||||
outer_query_builder
|
outer_query_builder
|
||||||
.execute_query::<PaymentMetricRow, _>(pool)
|
.execute_query::<PaymentMetricRow, _>(pool)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@ -458,17 +458,9 @@ pub struct GetDisputeMetricRequest {
|
|||||||
#[derive(Clone, Debug, Default, serde::Serialize)]
|
#[derive(Clone, Debug, Default, serde::Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub struct SankeyResponse {
|
pub struct SankeyResponse {
|
||||||
pub normal_success: i64,
|
pub count: i64,
|
||||||
pub normal_failure: i64,
|
pub status: String,
|
||||||
pub cancelled: i64,
|
pub refunds_status: Option<String>,
|
||||||
pub smart_retried_success: i64,
|
pub dispute_status: Option<String>,
|
||||||
pub smart_retried_failure: i64,
|
pub first_attempt: i64,
|
||||||
pub pending: i64,
|
|
||||||
pub partial_refunded: i64,
|
|
||||||
pub refunded: i64,
|
|
||||||
pub disputed: i64,
|
|
||||||
pub pm_awaited: i64,
|
|
||||||
pub customer_awaited: i64,
|
|
||||||
pub merchant_awaited: i64,
|
|
||||||
pub confirmation_awaited: i64,
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user