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,11 +273,17 @@ impl PaymentIntentMetricAccumulator for PaymentsDistributionAccumulator {
|
||||
}
|
||||
}
|
||||
|
||||
if status.as_ref() != &storage_enums::IntentStatus::RequiresCustomerAction
|
||||
&& 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn collect(self) -> Self::MetricOutput {
|
||||
if self.total == 0 {
|
||||
|
||||
@ -8,10 +8,9 @@ use api_models::analytics::{
|
||||
},
|
||||
GetPaymentIntentFiltersRequest, GetPaymentIntentMetricRequest, PaymentIntentFilterValue,
|
||||
PaymentIntentFiltersResponse, PaymentIntentsAnalyticsMetadata, PaymentIntentsMetricsResponse,
|
||||
SankeyResponse,
|
||||
};
|
||||
use bigdecimal::ToPrimitive;
|
||||
use common_enums::{Currency, IntentStatus};
|
||||
use common_enums::Currency;
|
||||
use common_utils::{errors::CustomResult, types::TimeRange};
|
||||
use currency_conversion::{conversion::convert, types::ExchangeRates};
|
||||
use error_stack::ResultExt;
|
||||
@ -24,7 +23,7 @@ use router_env::{
|
||||
use super::{
|
||||
filters::{get_payment_intent_filter_for_dimension, PaymentIntentFilterRow},
|
||||
metrics::PaymentIntentMetricRow,
|
||||
sankey::{get_sankey_data, SessionizerRefundStatus},
|
||||
sankey::{get_sankey_data, SankeyRow},
|
||||
PaymentIntentMetricsAccumulator,
|
||||
};
|
||||
use crate::{
|
||||
@ -51,7 +50,7 @@ pub async fn get_sankey(
|
||||
pool: &AnalyticsProvider,
|
||||
auth: &AuthInfo,
|
||||
req: TimeRange,
|
||||
) -> AnalyticsResult<SankeyResponse> {
|
||||
) -> AnalyticsResult<Vec<SankeyRow>> {
|
||||
match pool {
|
||||
AnalyticsProvider::Sqlx(_) => Err(AnalyticsError::NotImplemented(
|
||||
"Sankey not implemented for sqlx",
|
||||
@ -62,69 +61,7 @@ pub async fn get_sankey(
|
||||
let sankey_rows = get_sankey_data(ckh_pool, auth, &req)
|
||||
.await
|
||||
.change_context(AnalyticsError::UnknownError)?;
|
||||
let mut sankey_response = SankeyResponse::default();
|
||||
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)
|
||||
Ok(sankey_rows)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ use common_utils::{
|
||||
};
|
||||
use error_stack::ResultExt;
|
||||
use router_env::logger;
|
||||
use time::PrimitiveDateTime;
|
||||
|
||||
use crate::{
|
||||
clickhouse::ClickhouseClient,
|
||||
@ -13,29 +12,19 @@ use crate::{
|
||||
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(
|
||||
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")]
|
||||
pub enum SessionizerRefundStatus {
|
||||
@ -45,13 +34,36 @@ pub enum SessionizerRefundStatus {
|
||||
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 count: i64,
|
||||
pub status: DBEnumWrapper<enums::IntentStatus>,
|
||||
#[serde(default)]
|
||||
pub refunds_status: Option<DBEnumWrapper<SessionizerRefundStatus>>,
|
||||
pub attempt_count: i64,
|
||||
pub count: i64,
|
||||
#[serde(default)]
|
||||
pub dispute_status: Option<DBEnumWrapper<SessionizerDisputeStatus>>,
|
||||
pub first_attempt: i64,
|
||||
}
|
||||
|
||||
impl TryInto<SankeyRow> for serde_json::Value {
|
||||
@ -90,7 +102,12 @@ pub async fn get_sankey_data(
|
||||
.change_context(MetricsError::QueryBuildingError)?;
|
||||
|
||||
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")
|
||||
.change_context(MetricsError::QueryBuildingError)?;
|
||||
|
||||
@ -112,7 +129,12 @@ pub async fn get_sankey_data(
|
||||
.change_context(MetricsError::QueryBuildingError)?;
|
||||
|
||||
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")
|
||||
.change_context(MetricsError::QueryBuildingError)?;
|
||||
|
||||
|
||||
@ -218,6 +218,12 @@ impl PaymentMetricAccumulator for PaymentsDistributionAccumulator {
|
||||
}
|
||||
}
|
||||
}
|
||||
if status.as_ref() != &storage_enums::AttemptStatus::AuthenticationFailed
|
||||
&& status.as_ref() != &storage_enums::AttemptStatus::PaymentMethodAwaited
|
||||
&& status.as_ref() != &storage_enums::AttemptStatus::DeviceDataCollectionPending
|
||||
&& status.as_ref() != &storage_enums::AttemptStatus::ConfirmationAwaited
|
||||
&& status.as_ref() != &storage_enums::AttemptStatus::Unresolved
|
||||
{
|
||||
if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) {
|
||||
self.total += total;
|
||||
if metrics.first_attempt.unwrap_or(false) {
|
||||
@ -228,6 +234,7 @@ impl PaymentMetricAccumulator for PaymentsDistributionAccumulator {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn collect(self) -> Self::MetricOutput {
|
||||
if self.total == 0 {
|
||||
|
||||
@ -160,11 +160,6 @@ where
|
||||
.switch()?;
|
||||
}
|
||||
|
||||
outer_query_builder
|
||||
.set_limit_by(5, &filtered_dimensions)
|
||||
.attach_printable("Error adding limit clause")
|
||||
.switch()?;
|
||||
|
||||
outer_query_builder
|
||||
.execute_query::<PaymentMetricRow, _>(pool)
|
||||
.await
|
||||
|
||||
@ -458,17 +458,9 @@ pub struct GetDisputeMetricRequest {
|
||||
#[derive(Clone, Debug, Default, serde::Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct SankeyResponse {
|
||||
pub normal_success: i64,
|
||||
pub normal_failure: i64,
|
||||
pub cancelled: i64,
|
||||
pub smart_retried_success: i64,
|
||||
pub smart_retried_failure: 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,
|
||||
pub count: i64,
|
||||
pub status: String,
|
||||
pub refunds_status: Option<String>,
|
||||
pub dispute_status: Option<String>,
|
||||
pub first_attempt: i64,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user