Skip to content

Commit a9b8985

Browse files
committed
Merge branch 'main' of github.com:juspay/hyperswitch into multiple-cred-cypress
* 'main' of github.com:juspay/hyperswitch: chore(version): 2024.11.27.0 fix(core): add payment_id as query param in merchant return url (#6665) feat(connector): [Netcetera] add sca exemption (#6611) feat(payments): propagate additional payment method data for google pay during MIT (#6644) feat: Added grpc based health check (#6441) feat(analytics): add `sessionized_metrics` for disputes analytics (#6573)
2 parents 8f10947 + d4b482c commit a9b8985

File tree

35 files changed

+893
-85
lines changed

35 files changed

+893
-85
lines changed

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,34 @@ All notable changes to HyperSwitch will be documented here.
44

55
- - -
66

7+
## 2024.11.27.0
8+
9+
### Features
10+
11+
- **analytics:** Add `sessionized_metrics` for disputes analytics ([#6573](https://github.com/juspay/hyperswitch/pull/6573)) ([`8fbb766`](https://github.com/juspay/hyperswitch/commit/8fbb7663089d4790628109944e5fb5a57ccdaf00))
12+
- **connector:**
13+
- [INESPAY] add Connector Template Code ([#6614](https://github.com/juspay/hyperswitch/pull/6614)) ([`710186f`](https://github.com/juspay/hyperswitch/commit/710186f035c92a919e8f5a49565c6f8908f1803f))
14+
- [Netcetera] add sca exemption ([#6611](https://github.com/juspay/hyperswitch/pull/6611)) ([`3120494`](https://github.com/juspay/hyperswitch/commit/31204941ee24fe7b23344ba9b4a2615c46f33bb0))
15+
- **payments:** Propagate additional payment method data for google pay during MIT ([#6644](https://github.com/juspay/hyperswitch/pull/6644)) ([`75fe9c0`](https://github.com/juspay/hyperswitch/commit/75fe9c0c285f640967af33b1d969af9ce48c5b17))
16+
- **router:** [Cybersource] add PLN to the currency config ([#6628](https://github.com/juspay/hyperswitch/pull/6628)) ([`29a0885`](https://github.com/juspay/hyperswitch/commit/29a0885a8fc7b718f8b87866e2638e8bfad3c8f3))
17+
- **users:** Send welcome to community email in magic link signup ([#6639](https://github.com/juspay/hyperswitch/pull/6639)) ([`03423a1`](https://github.com/juspay/hyperswitch/commit/03423a1f76d324453052da985f998fd3f957ce90))
18+
- Added grpc based health check ([#6441](https://github.com/juspay/hyperswitch/pull/6441)) ([`e922f96`](https://github.com/juspay/hyperswitch/commit/e922f96cee7e34493f0022b0c56455357eddc4f8))
19+
20+
### Bug Fixes
21+
22+
- **core:** Add payment_id as query param in merchant return url ([#6665](https://github.com/juspay/hyperswitch/pull/6665)) ([`6829478`](https://github.com/juspay/hyperswitch/commit/682947866e6afc197c71bbd255f22ae427704590))
23+
24+
### Refactors
25+
26+
- **authn:** Enable cookies in Integ ([#6599](https://github.com/juspay/hyperswitch/pull/6599)) ([`02479a1`](https://github.com/juspay/hyperswitch/commit/02479a12b18dc68e2787ae237580fcb46348374e))
27+
- **connector:** Add amount conversion framework to Riskified ([#6359](https://github.com/juspay/hyperswitch/pull/6359)) ([`acb30ef`](https://github.com/juspay/hyperswitch/commit/acb30ef6d144eaf13b237b830d1ac534259932a3))
28+
- **payments_v2:** Use batch encryption for intent create and confirm intent ([#6589](https://github.com/juspay/hyperswitch/pull/6589)) ([`108b160`](https://github.com/juspay/hyperswitch/commit/108b1603fa44b2a56c278196edb5a1f76f5d3d03))
29+
- **tenant:** Use tenant id type ([#6643](https://github.com/juspay/hyperswitch/pull/6643)) ([`c9df7b0`](https://github.com/juspay/hyperswitch/commit/c9df7b0557889c88ea20392dfe56bf651e22c9a7))
30+
31+
**Full Changelog:** [`2024.11.26.0...2024.11.27.0`](https://github.com/juspay/hyperswitch/compare/2024.11.26.0...2024.11.27.0)
32+
33+
- - -
34+
735
## 2024.11.26.0
836

937
### Features

config/config.example.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,3 +790,4 @@ connector_list = "cybersource" # Supported connectors for network tokenization
790790
[grpc_client.dynamic_routing_client] # Dynamic Routing Client Configuration
791791
host = "localhost" # Client Host
792792
port = 7000 # Client Port
793+
service = "dynamo" # Service name

config/deployments/env_specific.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,3 +326,4 @@ check_token_status_url= "" # base url to check token status from token servic
326326
[grpc_client.dynamic_routing_client] # Dynamic Routing Client Configuration
327327
host = "localhost" # Client Host
328328
port = 7000 # Client Port
329+
service = "dynamo" # Service name

crates/analytics/src/clickhouse.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ impl AnalyticsDataSource for ClickhouseClient {
139139
| AnalyticsCollection::Dispute => {
140140
TableEngine::CollapsingMergeTree { sign: "sign_flag" }
141141
}
142+
AnalyticsCollection::DisputeSessionized => {
143+
TableEngine::CollapsingMergeTree { sign: "sign_flag" }
144+
}
142145
AnalyticsCollection::SdkEvents
143146
| AnalyticsCollection::SdkEventsAnalytics
144147
| AnalyticsCollection::ApiEvents
@@ -439,6 +442,7 @@ impl ToSql<ClickhouseClient> for AnalyticsCollection {
439442
Self::ConnectorEvents => Ok("connector_events_audit".to_string()),
440443
Self::OutgoingWebhookEvent => Ok("outgoing_webhook_events_audit".to_string()),
441444
Self::Dispute => Ok("dispute".to_string()),
445+
Self::DisputeSessionized => Ok("sessionizer_dispute".to_string()),
442446
Self::ActivePaymentsAnalytics => Ok("active_payments".to_string()),
443447
}
444448
}

crates/analytics/src/disputes/accumulators.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use super::metrics::DisputeMetricRow;
55
#[derive(Debug, Default)]
66
pub struct DisputeMetricsAccumulator {
77
pub disputes_status_rate: RateAccumulator,
8-
pub total_amount_disputed: SumAccumulator,
9-
pub total_dispute_lost_amount: SumAccumulator,
8+
pub disputed_amount: DisputedAmountAccumulator,
9+
pub dispute_lost_amount: DisputedAmountAccumulator,
1010
}
1111
#[derive(Debug, Default)]
1212
pub struct RateAccumulator {
@@ -17,7 +17,7 @@ pub struct RateAccumulator {
1717
}
1818
#[derive(Debug, Default)]
1919
#[repr(transparent)]
20-
pub struct SumAccumulator {
20+
pub struct DisputedAmountAccumulator {
2121
pub total: Option<i64>,
2222
}
2323

@@ -29,7 +29,7 @@ pub trait DisputeMetricAccumulator {
2929
fn collect(self) -> Self::MetricOutput;
3030
}
3131

32-
impl DisputeMetricAccumulator for SumAccumulator {
32+
impl DisputeMetricAccumulator for DisputedAmountAccumulator {
3333
type MetricOutput = Option<u64>;
3434
#[inline]
3535
fn add_metrics_bucket(&mut self, metrics: &DisputeMetricRow) {
@@ -92,8 +92,8 @@ impl DisputeMetricsAccumulator {
9292
disputes_challenged: challenge_rate,
9393
disputes_won: won_rate,
9494
disputes_lost: lost_rate,
95-
total_amount_disputed: self.total_amount_disputed.collect(),
96-
total_dispute_lost_amount: self.total_dispute_lost_amount.collect(),
95+
disputed_amount: self.disputed_amount.collect(),
96+
dispute_lost_amount: self.dispute_lost_amount.collect(),
9797
total_dispute,
9898
}
9999
}

crates/analytics/src/disputes/core.rs

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use api_models::analytics::{
55
DisputeDimensions, DisputeMetrics, DisputeMetricsBucketIdentifier,
66
DisputeMetricsBucketResponse,
77
},
8-
AnalyticsMetadata, DisputeFilterValue, DisputeFiltersResponse, GetDisputeFilterRequest,
9-
GetDisputeMetricRequest, MetricsResponse,
8+
DisputeFilterValue, DisputeFiltersResponse, DisputesAnalyticsMetadata, DisputesMetricsResponse,
9+
GetDisputeFilterRequest, GetDisputeMetricRequest,
1010
};
1111
use error_stack::ResultExt;
1212
use router_env::{
@@ -30,7 +30,7 @@ pub async fn get_metrics(
3030
pool: &AnalyticsProvider,
3131
auth: &AuthInfo,
3232
req: GetDisputeMetricRequest,
33-
) -> AnalyticsResult<MetricsResponse<DisputeMetricsBucketResponse>> {
33+
) -> AnalyticsResult<DisputesMetricsResponse<DisputeMetricsBucketResponse>> {
3434
let mut metrics_accumulator: HashMap<
3535
DisputeMetricsBucketIdentifier,
3636
DisputeMetricsAccumulator,
@@ -87,14 +87,17 @@ pub async fn get_metrics(
8787
logger::debug!(bucket_id=?id, bucket_value=?value, "Bucket row for metric {metric}");
8888
let metrics_builder = metrics_accumulator.entry(id).or_default();
8989
match metric {
90-
DisputeMetrics::DisputeStatusMetric => metrics_builder
90+
DisputeMetrics::DisputeStatusMetric
91+
| DisputeMetrics::SessionizedDisputeStatusMetric => metrics_builder
9192
.disputes_status_rate
9293
.add_metrics_bucket(&value),
93-
DisputeMetrics::TotalAmountDisputed => metrics_builder
94-
.total_amount_disputed
95-
.add_metrics_bucket(&value),
96-
DisputeMetrics::TotalDisputeLostAmount => metrics_builder
97-
.total_dispute_lost_amount
94+
DisputeMetrics::TotalAmountDisputed
95+
| DisputeMetrics::SessionizedTotalAmountDisputed => {
96+
metrics_builder.disputed_amount.add_metrics_bucket(&value)
97+
}
98+
DisputeMetrics::TotalDisputeLostAmount
99+
| DisputeMetrics::SessionizedTotalDisputeLostAmount => metrics_builder
100+
.dispute_lost_amount
98101
.add_metrics_bucket(&value),
99102
}
100103
}
@@ -105,18 +108,31 @@ pub async fn get_metrics(
105108
metrics_accumulator
106109
);
107110
}
111+
let mut total_disputed_amount = 0;
112+
let mut total_dispute_lost_amount = 0;
108113
let query_data: Vec<DisputeMetricsBucketResponse> = metrics_accumulator
109114
.into_iter()
110-
.map(|(id, val)| DisputeMetricsBucketResponse {
111-
values: val.collect(),
112-
dimensions: id,
115+
.map(|(id, val)| {
116+
let collected_values = val.collect();
117+
if let Some(amount) = collected_values.disputed_amount {
118+
total_disputed_amount += amount;
119+
}
120+
if let Some(amount) = collected_values.dispute_lost_amount {
121+
total_dispute_lost_amount += amount;
122+
}
123+
124+
DisputeMetricsBucketResponse {
125+
values: collected_values,
126+
dimensions: id,
127+
}
113128
})
114129
.collect();
115130

116-
Ok(MetricsResponse {
131+
Ok(DisputesMetricsResponse {
117132
query_data,
118-
meta_data: [AnalyticsMetadata {
119-
current_time_range: req.time_range,
133+
meta_data: [DisputesAnalyticsMetadata {
134+
total_disputed_amount: Some(total_disputed_amount),
135+
total_dispute_lost_amount: Some(total_dispute_lost_amount),
120136
}],
121137
})
122138
}

crates/analytics/src/disputes/metrics.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod dispute_status_metric;
2+
mod sessionized_metrics;
23
mod total_amount_disputed;
34
mod total_dispute_lost_amount;
45

@@ -92,6 +93,21 @@ where
9293
.load_metrics(dimensions, auth, filters, granularity, time_range, pool)
9394
.await
9495
}
96+
Self::SessionizedTotalAmountDisputed => {
97+
sessionized_metrics::TotalAmountDisputed::default()
98+
.load_metrics(dimensions, auth, filters, granularity, time_range, pool)
99+
.await
100+
}
101+
Self::SessionizedDisputeStatusMetric => {
102+
sessionized_metrics::DisputeStatusMetric::default()
103+
.load_metrics(dimensions, auth, filters, granularity, time_range, pool)
104+
.await
105+
}
106+
Self::SessionizedTotalDisputeLostAmount => {
107+
sessionized_metrics::TotalDisputeLostAmount::default()
108+
.load_metrics(dimensions, auth, filters, granularity, time_range, pool)
109+
.await
110+
}
95111
}
96112
}
97113
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use std::collections::HashSet;
2+
3+
use api_models::analytics::{
4+
disputes::{DisputeDimensions, DisputeFilters, DisputeMetricsBucketIdentifier},
5+
Granularity, TimeRange,
6+
};
7+
use common_utils::errors::ReportSwitchExt;
8+
use error_stack::ResultExt;
9+
use time::PrimitiveDateTime;
10+
11+
use super::DisputeMetricRow;
12+
use crate::{
13+
enums::AuthInfo,
14+
query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
15+
types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
16+
};
17+
#[derive(Default)]
18+
pub(crate) struct DisputeStatusMetric {}
19+
20+
#[async_trait::async_trait]
21+
impl<T> super::DisputeMetric<T> for DisputeStatusMetric
22+
where
23+
T: AnalyticsDataSource + super::DisputeMetricAnalytics,
24+
PrimitiveDateTime: ToSql<T>,
25+
AnalyticsCollection: ToSql<T>,
26+
Granularity: GroupByClause<T>,
27+
Aggregate<&'static str>: ToSql<T>,
28+
Window<&'static str>: ToSql<T>,
29+
{
30+
async fn load_metrics(
31+
&self,
32+
dimensions: &[DisputeDimensions],
33+
auth: &AuthInfo,
34+
filters: &DisputeFilters,
35+
granularity: &Option<Granularity>,
36+
time_range: &TimeRange,
37+
pool: &T,
38+
) -> MetricsResult<HashSet<(DisputeMetricsBucketIdentifier, DisputeMetricRow)>>
39+
where
40+
T: AnalyticsDataSource + super::DisputeMetricAnalytics,
41+
{
42+
let mut query_builder = QueryBuilder::new(AnalyticsCollection::DisputeSessionized);
43+
44+
for dim in dimensions {
45+
query_builder.add_select_column(dim).switch()?;
46+
}
47+
48+
query_builder.add_select_column("dispute_status").switch()?;
49+
50+
query_builder
51+
.add_select_column(Aggregate::Count {
52+
field: None,
53+
alias: Some("count"),
54+
})
55+
.switch()?;
56+
query_builder
57+
.add_select_column(Aggregate::Min {
58+
field: "created_at",
59+
alias: Some("start_bucket"),
60+
})
61+
.switch()?;
62+
query_builder
63+
.add_select_column(Aggregate::Max {
64+
field: "created_at",
65+
alias: Some("end_bucket"),
66+
})
67+
.switch()?;
68+
69+
filters.set_filter_clause(&mut query_builder).switch()?;
70+
71+
auth.set_filter_clause(&mut query_builder).switch()?;
72+
73+
time_range.set_filter_clause(&mut query_builder).switch()?;
74+
75+
for dim in dimensions {
76+
query_builder.add_group_by_clause(dim).switch()?;
77+
}
78+
79+
query_builder
80+
.add_group_by_clause("dispute_status")
81+
.switch()?;
82+
83+
if let Some(granularity) = granularity.as_ref() {
84+
granularity
85+
.set_group_by_clause(&mut query_builder)
86+
.switch()?;
87+
}
88+
89+
query_builder
90+
.execute_query::<DisputeMetricRow, _>(pool)
91+
.await
92+
.change_context(MetricsError::QueryBuildingError)?
93+
.change_context(MetricsError::QueryExecutionFailure)?
94+
.into_iter()
95+
.map(|i| {
96+
Ok((
97+
DisputeMetricsBucketIdentifier::new(
98+
i.dispute_stage.as_ref().map(|i| i.0),
99+
i.connector.clone(),
100+
TimeRange {
101+
start_time: match (granularity, i.start_bucket) {
102+
(Some(g), Some(st)) => g.clip_to_start(st)?,
103+
_ => time_range.start_time,
104+
},
105+
end_time: granularity.as_ref().map_or_else(
106+
|| Ok(time_range.end_time),
107+
|g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
108+
)?,
109+
},
110+
),
111+
i,
112+
))
113+
})
114+
.collect::<error_stack::Result<
115+
HashSet<(DisputeMetricsBucketIdentifier, DisputeMetricRow)>,
116+
crate::query::PostProcessingError,
117+
>>()
118+
.change_context(MetricsError::PostProcessingFailure)
119+
}
120+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
mod dispute_status_metric;
2+
mod total_amount_disputed;
3+
mod total_dispute_lost_amount;
4+
pub(super) use dispute_status_metric::DisputeStatusMetric;
5+
pub(super) use total_amount_disputed::TotalAmountDisputed;
6+
pub(super) use total_dispute_lost_amount::TotalDisputeLostAmount;
7+
8+
pub use super::{DisputeMetric, DisputeMetricAnalytics, DisputeMetricRow};

0 commit comments

Comments
 (0)